Zsh returning `<function>:<linenumber> = not found` - zsh

I used the have the following tmux shortcut function defined in a separate script and aliased, which worked fine but was messy. I decided to move it to my .zshrc where it naturally belongs, and encountered a problem I wasn't able to figure out.
function t () {re='^[0-9]+$'
if [ "$1" == "kill" ]
then
tmux kill-session -t $2
elif [[ "$1" =~ "$re" ]]
then
tmux attach-session -d -t $1
fi}
I source my .zshrc, call the function, and get:
t:1: = not found
I know the function is defined:
╭─bennett#Io [~] using
╰─○ which t
t () {
re='^[0-9]+$'
if [ "$1" == "kill" ]
then
tmux kill-session -t $2
elif [[ "$1" =~ "$re" ]]
then
tmux attach-session -d -t $1
fi
}
I'm assuming this is complaining about the first line of the function. I've tried shifting the first line of the function down several lines, which doesn't change anything except which line the error message refers to. Any clue what's going on? I haven't found anything relating to this specific issue on SO.

The command [ (or test) only supports a single = to check for equality of two strings. Using == will result in a "= not found" error message. (See man 1 test)
zsh has the [ builtin mainly for compatibility reasons. It tries to implement POSIX where possible, with all the quirks this may bring (See the Zsh Manual).
Unless you need a script to be POSIX compliant (e.g. for compatibility with other shells), I would strongly suggest to use conditional expressions, that is [[ ... ]], instead of [ ... ]. It has more features, does not require quotes or other workarounds for possibly empty values and even allows to use arithmetic expressions.

Wrapping the first conditional in a second set of square-brackets seemed to resolve the issue.
More information on single vs double brackets here:
Is [[ ]] preferable over [ ] in bash scripts?

Related

Combining file tests in Zsh

What is the most elegant way in zsh to test, whether a file is either a readable regular file?
I understand that I can do something like
if [[ -r "$name" && -f "$name" ]]
...
But it requires repeating "$name" twice. I know that we can't combine conditions (-rf $name), but maybe some other feature in zsh could be used?
By the way, I considered also something like
if ls ${name}(R.) >/dev/null 2>&1
...
But in this case, the shell would complain "no matches found", when $name does not fulfil the criterium. Setting NULL_GLOB wouldn't help here either, because it would just replace the pattern with an empty string, and the expression would always be true.
In very new versions of zsh (works for 5.0.7, but not 5.0.5) you could do this
setopt EXTENDED_GLOB
if [[ -n $name(#qNR.) ]]
...
$name(#qNR.) matches files with name $name that are readable (R) and regular (.). N enables NULL_GLOB for this match. That is, if no files match the pattern it does not produce an error but is removed from the argument list. -n checks if the match is in fact non-empty. EXTENDED_GLOB is needed to enable the (#q...) type of extended globbing which in turn is needed because parenthesis usually have a different meaning inside conditional expressions ([[ ... ]]).
Still, while it is indeed possible to write something up that uses $name only once, I would advice against it. It is rather more convoluted than the original solution and thus harder to understand (i.e. needs thinking) for the next guy that reads it (your future self counts as "next guy" after at most half a year). And at least this solution will work only on zsh and there only on new versions, while the original would run unaltered on bash.
How about make small(?) shell functions as you mentioned?
tests-raw () {
setopt localoptions no_ksharrays
local then="$1"; shift
local f="${#[-1]}" t=
local -i ret=0
set -- "${#[1,-2]}"
for t in ${#[#]}; do
if test "$t" "$f"; then
ret=$?
"$then"
else
return $?
fi
done
return ret
}
and () tests-raw continue "${#[#]}";
or () tests-raw break "${#[#]}";
# examples
name=/dev/null
if and -r -c "$name"; then
echo 'Ok, it is a readable+character special file.'
fi
#>> Ok, it is...
and -r -f ~/.zshrc ; echo $? #>> 0
or -r -d ~/.zshrc ; echo $? #>> 0
and -r -d ~/.zshrc ; echo $? #>> 1
# It could be `and -rd ~/.zshrc` possible.
I feel this is somewhat overkill though.

How to create options in KSH script

I am creating a KSH interface script that will call other scripts based on the users input. The other scripts are Encrypt and Decrypt. Each one of these scripts receive parameters. I have seen someone execute a script before using "-" + first letter of a script name before. How do I do this for my script? So for example if my script is called menu and the user typed in : menu -e *UserID Filename.txt* the script would run and the encrypt script would be executed along with the associated parameters. So far my script takes in the encrypt/decrypt script option as a parameter. Here is my script:
#!/bin/ksh
#I want this parameter to become an
action=$1
if [ $1 = "" ]
then
print_message "Parameters not satisfied"
exit 1
fi
#check for action commands
if [ $1 = "encrypt" ]
then
dest=$2
fileName=$3
./Escript $dest $fileName
elif [ $1 = "decrypt" ]
then
outputF=$2
encryptedF=$3
./Dscript $outputF $encryptedF
else
print "Parameters not satisfied. Please enter encrypt or decrypt plus-n arguments"
fi
Thanks for the help!
There isn't any kind of automatic way to turn a parameter into another script to run; what you're doing is pretty much how you would do it. Check the parameter, and based on the contents, run the two different scripts.
You can structure it somewhat more nicely using case, and you can pass the later parameters directly through to the other script using "$#", with a shift to strip off the first parameter. Something like:
[ $# -ge 1 ] || (echo "Not enough parameters"; exit 1)
command=$1
shift
case $command in
-e|--encrypt) ./escript "$#" ;;
-d|--decrypt) ./dscript "$#" ;;
*) echo "Unknown option $command"; exit 1 ;;
esac
This also demonstrates how you can implement both short and long options, by providing two different strings to match against in a single case statement (-e and --encrypt), in case that's what you were asking about. You can also use globs, like -e*) to allow any option starting with -e such as -e, -encrypt, -elephant, though this may not be what you're looking for.

Capturing and testing output command in ZSH

I have tried countless ways to get what I want, but nothing seems to work. I always end up with something like 2:not found.
I want to capture the output of a command, and then test if it equals "!", like so:
function test() {
local testv=$(command) 2>/dev/null
if [ $(#testv) == "!" ]; then
echo "Exclamation mark!"
else
echo "No exclamation mark."
fi
}
How should I rewrite the code above to avoid the error test:2: = not found?
This should work:
if [ $testv = '!' ]; then
There were several problems here:
$(...) runs a command and substitutes its output; you want to substitute a variable value, so use $var or ${var}.
I have no idea what the # was doing there. ${#var} will get the length of $var, but that's not what you want here.
The test command (which [ is a synonym for) doesn't understand ==, so use = (if you're a C programmer that'll look wrong, but this is shell not C).
I don't think this is a problem in a script, but for interactive input "!" doesn't do what you expect. I used '!' to make sure the exclamation mark couldn't be interpreted as a history option.
Alternately, you could use [[ ]] instead of [ ], since it understands == (and has somewhat cleaner syntax in general):
if [[ $testv == '!' ]]; then
BTW, I'm trusting from the tag that this script is running in zsh; if not, the syntax will be a bit different (basic shells don't have [[ ]], and anything other than zsh will do unwanted parsing on the value of $testv unless it's in double-quotes). If you're not sure (or want it to be portable), here's a version that should work in any posix-compliant shell:
if [ "$testv" = '!' ]; then
Try with this:
local testv=$(command 2>/dev/null)
since it's the output of the command you want to redirect.
(I have no idea what you mean by $(#testv) though.)

unix script error

I have the following line in my unix script file:
if [[ -f $DIR1/$FILE1 ] -a [ -f $DIR1/$FILE2 ]]; then
As clear the line checks for existence of two files in a directory and if both the files are present, some logic will be executed.
However, on running the script I am getting the following error on above line:
test_script: line 30: syntax error at line 54: `]' unexpected
line 54 is where above line is present.
What does this error mean ? Where am I wrong ?
Thanks for reading!
For the most common shells at least, [] are not like parentheses in C where you use then to group subexpressions.
What you need is something like (for bash):
if [[ -f $DIR1/$FILE1 && -f $DIR1/$FILE2 ]]; then
If you want help with a specific (non-bash) shell, you should let us know which one you're using.
There is no need of [] with -f.
if [ -f $DIR1/$FILE1 -a -f $DIR1/$FILE2 ]; then
Output:
shadyabhi#archlinux /tmp $ touch foo;touch foo2
shadyabhi#archlinux /tmp $ if [ -f "foo" -a -f "foo2" ]; then echo "Hello"; fi
Hello
shadyabhi#archlinux /tmp $
It's interesting that there are multiple answers explaining the subtle differences between [ and [[, but for some reason our culture seems to discourage people from providing the obvious solution. Stop using '[' entirely. Instead of '[', use test:
if test -f $DIR1/$FILE1 && test -f $DIR1/$FILE2; then
Test is cleaner syntax than '[', which requires a final ']' argument and continually confuses people into thinking that the brackets are part of the language. '[[' is not portable and confuses people who don't realize that many shells provide extra functionality that is non-standard. There is a case to be made that [[ can be more efficient than [, but if run-time performance is a problem in your shell, you probably shouldn't be solving the problem in sh.
You had extra [ and ]
if [ -f $DIR1/$FILE1 -a -f $DIR1/$FILE2 ]; then
Basically, you were mixing two syntax that aim to do the same thing: namely [ ] and [[ ]]. The former is more portable but the latter is more powerful; although the majority of shells you would come across do support [[ ]].
But better still is the following since you are already using the [[ ]] construct
if [[ -f $DIR1/$FILE1 && -f $DIR1/$FILE2 ]]; then
As #paxdiablo stated, you can use it this way:
if -f $DIR1/$FILE1 && -f $DIR1/$FILE2 ; then
or you can use it this way:
if -f $DIR1/$FILE || -f $DIR1/$FILE2 ;

How to compare two files in shell script?

Here is my scenario.
I have two files which are having records with each record's 3-25 characters is an identifier. Based on this I need to compare both of them and update the old file with the new file data if their identifiers match. Identifiers start with 01.
Please look at the script below.
This is giving some error as "argument expected at line 12 which I am not able to understand.
#!/bin/ksh
while read line
do
c=`echo $line|grep '^01' `
if [ $c -ne NULL ];
then
var=`echo $line|cut -c 3-25`
fi
while read i
do
d=`echo $i|grep '^01' `
if [ $d -ne NULL ];
then
var1=`echo $i|cut -c 3-25`
if [ $var -eq $var1 ];
then
$line=$i
fi
fi
done < test_monday
done < test_sunday
Please help me out thanks in advance
I think what you need is :
if [ "$d" != NULL ];
Try.
I think you could use the DIFF command
diff file1 file2 > whats_the_diff.txt
Unless you are writing a script for portability to the original Bourne shell or others that do not support the feature, in Bash and ksh you should use the [[ form of test for strings and files.
There is a reduced need for quoting and escaping, additional conditions such as pattern and regular expression matching and the ability to use && and || instead of -a and -o.
if [[ $var == $var1 ]]
Also, "NULL" is not a special value in Bash and ksh and so your test will always succeed since $d is tested against the literal string "NULL".
if [[ $d != "" ]]
or
if [[ $d ]]
For numeric values (not including leading zeros unless you're using octal), you can use numeric expressions. You can omit the dollar sign for variables in this context.
numval=41
if ((++numval >= 42)) # increment then test
then
echo "don't panic"
fi
It's not necessary to use echo and cut for substrings. In Bash and ksh you can do:
var=${line:3:23}
Note: cut uses character positions for the beginning and end of a range, while this shell construct uses starting position and character count so you have to adjust the numbers accordingly.
And it's a good idea to get away from using backticks. Use $() instead. This can be nested and quoting and escaping is reduced or easier.

Resources