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 ;
Related
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?
I want to know which permission is given to a file using a shell script. So i used the below code to test for a file. But it shows nothing in output. I just wanted to know where i have made the mistake. Please help me.
The file "1.py" has all read write and execute files enabled.
ls -l 1.py | awk ' {if($1 -eq "-rwxrwxrwx")print 'True'; }'
The single quotes (') around True should be double quotes ("), and awk uses == for string comparison.
However, depending on what you're trying to do, it might be cleaner to use the Bash builtin tests:
if [ -r 1.py -a -x 1.py ]; then
echo "Yes, we can read (-r) and (-a) execute (-x) the file"
else
echo "No, we can't."
fi
This avoids having to parse ls output. For a longer list of checks, see tldp.org.
in awk, you shouldn't write shell test, e.g. [[ ... -eq ...]], you should do it in awk way:
if($1=="whatever")...
you could use
ls -l 1.py | awk '{if ($1 == "-rwxrwxrwx") print "True" }'
I came across this line in one of the shell scripts:
[-f $host_something ] && .$host_something
What are the square brackets with the -f switch supposed to do, and what is the point of ANDing it with the same environment variable?
The [ is actually an actual binary. It's an alias for the test(1) command. It will ignore it's last argument which should be ]. Run man test for further information. It's not really shell syntax.
The square bracket is really an alias for the test tool, so you can look at man test to find out how it works. the -f switch is one of many tests that can be run by this tool, and tests if a file exists and is a regular file.
You need some more spaces.
The command
[ -f $host_something ] && . $host_something
stands for
if [ -f $host_something ]; then
source $host_something
fi
or in words:
When the file given in the variable host_something really is a file, then execute the lines in that file without opening a subshell. You do not want a subshell, since all the settings in the subshell get lost as soon as the subshell is finished.
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.
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.