Bash : how to squeeze these 2 if statements into one - bash

I am new to Bash and scripting and I want to figure out a way to combine these two statements into 1 . what this script does is checks if two files D1 and D2 are the same file and if not checks if their content is the same.
if [ ! $D1 -ef $D2 ]
then
echo not the same file
if cmp -s $D1 $D2
then
echo same info
else
echo not same info
fi
else
echo same file
fi
In addition to this, I am also confused when to use [] and when to skip them, manual says when its a conditional use [], but what does that mean ?
Thank you.

The syntax of an if statement is (from 2.10 Shell Grammar):
if_clause : If compound_list Then compound_list else_part Fi
| If compound_list Then compound_list Fi
Where compound_list eventually gets down to a command.
! $D1 -ef $D2 is not a command.
[ is a a command (also known as test). See the output from type [ or type test as well as which [ and which test.
So [ ! $D1 -ef $D2 ] is a valid command used in the if statement.
The return value of the compound_list is what if tests.
So when you are using something like cmp (or any other command) then there is no reason to use [ and, in fact, using [ is incorrect.
As the compound_list can be more than a single command to combine [ ! $D1 -ef $D2 ] and cmp -s $D1 $D2 simply use && as normal. (Need ! on the cmp call too to invert that to get the "not same" test from both.)
if [ ! "$D1" -ef "$D2" ] && ! cmp -s "$D1" "$D2"; then
echo 'Not same file or same contents'
fi

Related

bash unary operator expected error

This is the script I came up with
#!/bin/bash
expression=$1
field=$2
if [ -z "$expression" ]; then
echo "expression is missing"
exit 1
fi
if [ -f /home/miked/table ]; then
if [ -f table ] && [ grep "$expression table" ]; then
grep "$expression" table | cut -d: -f${2:-3} | clipit
else
echo "no match found"
fi
else
echo "there is no table file"
fi
As a matter of fact I know how to fix it, but I don't know why
it's fixed.
If I remove the space between grep and ", all is working fine, I just can't seem to understand why.
If I do grep something file directly to the command line, it's working good. Why do I to stick the grep to the " in the script?
You do not need to wrap grep call inside square brackets. [ is alias to test command (in general, however most shells replicate that using builtin). You can see syntax of that command using man test. What you want do is to check if $expression table exist in some file, so instead you need to write it as:
#!/bin/bash
expression="$1"
field="$2"
if [ -z "$expression" ]; then
echo "expression is missing"
exit 1
fi
if [ -f /home/miked/table ]; then
if [ -f table ] && grep "$expression table"; then
grep "$expression" table | cut -d: -f${2:-3} | clipit
else
echo "no match found"
fi
else
echo "there is no table file"
fi
However there are more problems with your script.
You print errors to stdout instead of stderr which makes them invisible when piping output of your script to other tools, instead you should use echo "error" >&2, ideally use separate function for that.
You pass only 1 argument to grep and I believe that there should be 2: grep "$expression" table.
Your first grep call will also print to stdout and I believe you want to surpass that, so instead use -q flag.
It is good idea to enable "exit on error" using set -e and "exit on pipe error" using set -o pipefail
You do not use outer file, so you can just remove check for that.
You do not use your $field variable.
Use guard clausules instead of ifs for fatal error checking as it will make easier to refactor your script.
So whole file could be written as:
#!/bin/bash
set -eo pipefail
perror() {
echo "$1" >&2 && exit 1
}
expression=$1
field=${2:-3}
file=${3:table}
[ -z "$expression" ] || perror "expression is missing"
[ -f "$file" ] || perror "there is no '$file' file"
grep "$expression" "$file" | cut -d: -f"${field}" | clipit || perror "no match found"
You don't need [ ] at all here. You're interested in the exit status of grep and it gives you one already; to suppress its output, you can use the -q option:
if [ -f table ] && grep -q "$expression" table; then
The filename should not be within the quotes, either.
If you use [ ] without any test, it defaults to -n: "true if string is not empty". This test expects a single argument, which is why it seems to work for you if you remove the space: it just checks if the string grep$expression table expands to is non-zero, and it always is.
There are quite a few things that could be done to fix this, your main problem is the following line:
if [ -f table ] && [ grep "$expression table" ]; then
You've already tested if "table" exists, so you're doing it again and once it succeeds, the expression [ grep "$expression table" ] is being evaluated, which is broken down to '[' grep 'expression table' ']' which means essentially nothing.
You should instead use $() and evaluate the number of occurrences, or like Benjamin mentions, skip it altogether if that's what you want.
I would suggest something like this
#!/bin/bash
expression=$1
field=$2
table_file=/home/miked/table
if [ -z "$expression" ]; then
echo "expression is missing"
exit 1
fi
if [ -f $table_file ]; then
if [ $(grep -q "$expression $table_file") -gt 0 ]; then
grep "$expression" $table_file | cut -d: -f${2:-3} | clipit
else
echo "no match found"
fi
else
echo "there is no table file"
fi
Notice how we're still using a test, this could be condensed to:
#!/bin/bash
expression=$1
field=$2
table_file=/home/miked/table
if [ -z "$expression" ]; then
echo "expression is missing"
exit 1
fi
if [ -f $table_file ]; then
grep -q $expression $table_file && grep "$expression" $table_file | cut -d: -f${2:-3} | clipit || echo "no match found"
else
echo "there is no table file"
fi

Arithmetic syntax error with shell script

I am trying to call the function "warn" if the calculation is TRUE. I am still not quite comfortable with the syntax, would like some tips on how to fix the last line.
if [ "$noproc" -gt 0 ]; then
echo "WARNING: NoProc at $noproc for $process processes." >> $log
elif [ "$max" -ge 11 ]; then
[ $(($max - $total)) -lt 6 && [ $idle -le $(($max \* 0.25 | bc -l)) ] ] | warn $total $process $max $idle
The error I get: line 97: [: missing ` ] '
If your tagging for this question is correct and you're genuinely using bash (which is to say that your script starts with #!/bin/bash, or if not started via a shebang you use bash yourscript rather than sh yourscript), you might as well take advantage of it.
# extended bash math syntax
if (( (max - total) < 6 )) && (( idle <= (max / 4) )); then
warn "$total" "$process" "$max" "$idle"
fi
If, for whatever reason, you don't want to use (( )), you can still use [[ ]], which gives you a test context with its own extended syntax:
# extended bash test syntax
if [[ $((max - total)) -lt 6 && $idle -le $(bc -l <<<"$max*0.25") ]]; then
warn "$total" "$process" "$max" "$idle"
fi
...whereas if you want to be compatible with POSIX sh, you need to end the test before you can put in a shell-level logical-AND operator.
# corrected POSIX-compliant test syntax
if [ "$((max - total))" -lt 6 ] && [ "$idle" -le "$(bc -l <<<"$max*0.25")" ]; then
warn "$total" "$process" "$max" "$idle"
fi
To understand why, let's look at how your original command would parse, if you changed the (utterly incorrect) | symbol to && instead:
# Equivalent (longer form) of the original code, with pipe corrected to logical-AND
if [ $(($max - $total)) -lt 6; then
if [ $idle -le $(($max \* 0.25 | bc -l)) ] ]; then
warn $total $process $max $idle
fi
fi
Note that this is running, as a single command, [ $(($max - $total)) -lt 6.
[ is not special shell syntax -- it's just a command. In older shells it was actually /usr/bin/[; today, there's also a [ builtin as well, but other than being faster to execute, it behaves exactly the same way as it would have were it executing the old, external command.
That [ command expects to be passed a ] as its last argument, since there's no ] after the -lt 6, you get a syntax error and it exits.
Similarly, your code would then (if the first command succeeded) run [ $idle -le $(($max \* 0.25 | bc -l)) ] ]. Here, you have a [ command passed two ]s on the end; it simply doesn't know what to do with the second one.
You can't nest [ invocations. Even if you could, a && (b) === a && b in logic.
You can't use commands in arithmetic expansions.
Bash's [[ is safer than [.
Use More Quotes™.
Result:
[[ "$(($max - $total))" -lt 6 ]] && [[ "$idle" -le "$(bc -l <<< "$max * 0.25")" ]] | warn "$total $process $max $idle"

UNIX multiple if conditions

I have some problem with my code here. This is my code:
#!bin/sh
grep "$1" Rail.txt > test.txt
if [ "$#" -eq 1 -a grep -q "$1" test.txt ]; then
grep "$1" Rail.txt
else
echo not found
fi
Problem:
It says: script.sh: line 3: [: too many arguments every time I run it.
I'm not sure what's wrong with my condition whether I use the wrong operators or parenthesis or square brackets.
At a semi-educated guess, what you want to write is:
if [ "$#" -eq 1 ] && grep -q "$1" test.txt; then
On what ocassions should we use the square brackets?
Historically, the test command, /bin/test was linked to /bin/[, and was an external command, not a shell built-in. These days (and for several decades now), it has been a shell built-in. However, it follows the structure of a command, requiring spaces to separate arguments, and if it is invoked as [, then the last argument must be ].
As to when you use it, you use it when you need to test a condition.
Note that you can write:
if ls; false; true; then echo "the last command counts"; else echo "no it doesn't"; fi
The if command executes a sequence of one or more commands, and tests the exit status of the last command. If the exit status is 0, success, the then clause is executed; if the exit status is not 0, then the else clause is taken.
So, you can use the test when you need to test something. It can be part of an if or elif or while (or until). It can also be used on its own with || or &&:
[ -z "$1" ] && echo "No arguments - or the first argument is an empty string"
[ "$var1" -gt "$var2" ] || echo "Oops!" && exit 1
These could be written as if statements too, of course:
if [ -z "$1" ]
then echo "No arguments - or the first argument is an empty string"
fi
if [ "$var1" -le "$var2" ]
then
echo "Oops!"
exit 1
fi
Note that I needed to invert the test in the second rewrite. Bash has a built-in ! operator which inverts the exit status of the command that follows, so I could also have written:
if ! [ "$var1" -gt "$var2" ]
and test has a ! too, so it could also be written:
if [ ! "$var1" -gt "$var2" ]

How do I redirect warnings in bash?

I am trying to redirect warnings in a shell script. I know to redirect I have to do
command 2>/dev/null
But what I am redirecting is the warning from an if statement. Something like:
if [ "$a" -gt "$b" ] 2>/dev/null
But when I do that my output also goes into null. I mean, if the condition is true then normally I enter into the if-block and a warning msg get displayed "integer expression expected" and all works fine. But when I add the redirection, it just doesnt work. My guess is output is also being redirected. How do I make this work?
To suppress error generated by condition you can do:
[ "$a" -gt "$b" ] 2>/dev/null
However I strongly suggest you find better ways to make sure your variable are of correct type before entering into this condition.
UPDATE
As per comment here is simplistic version comparison script:
v1="$a"
v2="$b"
first=$(echo -e "$v1\n$v2" | sort -nr | head -1)
[[ "$first" = "$v1" ]] && echo "$v1 greater than $v2" || echo "$v1 same or smaller than $v2"
Instead of
if [ "$a" -gt "$b" ] 2>/dev/null
say:
if [ "$a" -gt "$b" 2>/dev/null ]
i.e. include the redirection within the test.
It's better to make use of the mathematical form instead:
(( a > b )) && echo "a is greater than b"

“unary operator expected” in shell script

I need a script to keep polling "receive_dir" directory till "stopfile" get written in the directory.
This has to run despite empty directory.
So far i have this but fails if receive_dir is empty with no files with "unary operator expected". Help !!
#!/usr/bin/ksh
until [ $i = stopfile ]
do
for i in `ls receive_dir`; do
time=$(date +%m-%d-%Y-%H:%M:%S)
echo $time
echo $i;
done
done
This will do what you ask for (loop until the stop file exist). I added a "sleep 1" to lower resource usage. It's also good practice to use "#!/usr/bin/env ksh" as shebang.
#!/usr/bin/env ksh
until [ -e receive_dir/stopfile ]
do
time=$(date +%m-%d-%Y-%H:%M:%S)
echo $time
sleep 1
done
If you have empty dir, the
until [ $i = stopfile ]
is evaluated as
until [ = stopfile ]
what is ofcourse syntax error.
One comment: Never parse output from ls.
#!/bin/bash
do_something() {
echo $(date +%m-%d-%Y-%H:%M:%S) "$1"
}
dir="."
until [[ -f "$dir/stopfile" ]]
do
find "$dir" -print0 | while IFS= read -r -d '' filename
do
do_something "$filename"
done
done
or (much slower)
do_something() {
echo $(date +%m-%d-%Y-%H:%M:%S) "$1"
}
export -f do_something
dir="."
until [[ -f "$dir/stopfile" ]]
do
find "$dir" -exec bash -c 'do_something "{}"' \;
done
You're evaluating nothing, and the 'test' isn't able to evaluate it.
~> [ $empty_var = stopfile ]
-bash: [: =: unary operator expected
First, don't parse ls:
http://mywiki.wooledge.org/BashPitfalls#for_i_in_.24.28ls_.2A.mp3.29
EDIT: Part of the issue is your loop is actually doing the test, try something like this (assuming receive_dir is relative):
#user000001 is right; my original find example would suffer the same issue, so this:
for i in receive_dir/*
do
time=$(date +%m-%d-%Y-%H:%M:%S)
echo $time
echo $i
[ $i = stopfile ] && break
done
EDIT: Adding in another example based on your comment:
How about this...
FOUND="N"
while [ "${FOUND}" = "N" ]
do
for i in receive_dir/*
do
time=$(date +%m-%d-%Y-%H:%M:%S)
echo $time
echo $i
[ "$i" = stopfile ] && FOUND="Y"
done
sleep 60
done
Another option is to use inotifywait to monitor the status of the directory. For example, this script will run the loop until the file "stopfile" is touched.
until inotifywait "receive_dir" | grep "stopfile"
do
echo "running"
done
echo "done"
The advantage is that these is no busy loop, and that you don't have to repeatedly call the (potentially expensive) find command

Resources