-n redundant in command -v tests? - bash

When testing for the existence of a command,
[ -n "$(command -v foo)" ]
and
[ "$(command -v foo)" ]
seem functionally equivalent, yet most examples I've seen include the -n test explicitly. I assume that if a command does not exist, that's equivalent to:
[ "" ]
Is it bad practice to omit -n?

Regardless of using -n or not, you are testing the output of the command builtin. I would test it's return value:
if command -v "$cmd" >/dev/null 2>&1 ; then
echo "command $cmd exists"
fi

Related

SoF2 shell script not running

I've got the following code in my shell script:
SERVER=`ps -ef | grep -v grep | grep -c sof2ded`
if ["$SERVER" != "0"]; then
echo "Already Running, exiting"
exit
else
echo "Starting up the server..."
cd /home/sof2/
/home/sof2/crons/start.sh > /dev/null 2>&1
fi
I did chmod a+x status.sh
Now I try to run the script but it's returning this error:
./status.sh: line 5: [1: command not found
Starting up the server...
Any help would be greatly appreciated.
Could you please try changing a few things in your script as follows and let me know if that helps you?(changed back-tick to $ and changed [ to [[ in code)
SERVER=$(ps -ef | grep -v grep | grep -c sof2ded)
if [[ "$SERVER" -eq 0 ]]; then
echo "Already Running, exiting"
exit
else
echo "Starting up the server..."
cd /home/sof2/
/home/sof2/crons/start.sh > /dev/null 2>&1
fi
The problem is with the test command. "But", I hear you say, "I am not using the test command". Yes you are, it is also known as [.
if statement syntax is if command. The brackets are not part of if syntax.
Commands have arguments separated (tokenized) by whitespace, so:
[ "$SERVER" != "0" ]
The whitespace is needed because the command is [ and then there are 4 arguments passed to it (the last one must be ]).
A more robust way of comparing numerics is to use double parentheses,
(( SERVER == 0 ))
Notice that you don't need the $ or the quotes around SERVER. Also the spacing is less important, but useful for readability.
[[ is used for comparing text patterns.
As a comment, backticks ` ` are considered deprecated because they are difficult to read, they are replaced with $( ... ).

While loop hangs and does not find string

I have a section of code in a bash script that uses a while loop to grep a file until the string I am looking for is there, then exit. Currently, its just hanging using the following code:
hostname="test-cust-15"
VAR1=$(/bin/grep -wo -m1 "HOST ALERT: $hostname;DOWN" /var/log/logfile)
while [ ! "$VAR1" ]
do
sleep 5
done
echo $VAR1 was found
I know the part of the script responsible for inserting this string into the logfile works, as I can grep it out side of the script and find it.
One thing I have tried is to change up the variables. Like this:
hostname="test-cust-15"
VAR1="HOST ALERT: $hostname;DOWN"
while [ ! /bin/grep "$VAR1" /var/log/logfile ]
do
sleep 5
done
echo $VAR1 was found
But i get a binary operator expected message and once I got a too many arguments message when using this:
while [ ! /bin/grep -q -wo "$VAR1" /var/log/logfile ]
What do I need to do to fix this?
while/until can work off of the exit status of a program directly.
until /bin/grep "$VAR1" /var/log/logfile
do
sleep 5
done
echo "$VAR1" was found
You also mentioned that it prints out the match in an above comment. If that's not desirable, use output redirection, or grep's -q option.
until /bin/grep "$VAR1" /var/log/logfile >/dev/null
until /bin/grep -q "$VAR1" /var/log/logfile
No need to bother with command substitution or test operator there. Simply:
while ! grep -wo -m1 "HOST ALERT: $hostname;DOWN" /var/log/logfile; do
sleep 5
done
Don't waste resources, use tail!
#!/bin/bash
while read line
do
echo $line
break
done < <(tail -f /tmp/logfile | grep --line-buffered "HOST ALERT")

Pass command via variable in shell

I have following code in my build script:
if [ -z "$1" ]; then
make -j10 $1 2>&1 | tee log.txt && notify-send -u critical -t 7 "BUILD DONE"
else
make -j10 $1 2>&1 | tee log.txt | grep -i --color "Error" && notify-send -u critical -t 7 "BUILD DONE"
fi
I tried to optimize it to:
local GREP=""
[[ ! -z "$1" ]] && GREP="| grep -i --color Error" && echo "Grepping for ERRORS"
make -j10 $1 2>&1 | tee log.txt "$GREP" && notify-send -u critical -t 7 "BUILD DONE"
But error thrown in make line if $1 isn't empty. I just can't figure out how to pass command with grep pipe through the variable.
Like others have already pointed out, you cannot, in general, expect a command in a variable to work. This is a FAQ.
What you can do is execute commands conditionally. Like this, for example:
( make -j10 $1 2>&1 && notify-send -u critical -t 7 "BUILD DONE" ) |
tee log.txt |
if [ -z "$1" ]; then
grep -i --color "Error"
else
cat
fi
This has the additional unexpected benefit that the notify-send is actually conditioned on the exit code of make (which is probably what you intended) rather than tee (which I would expect to succeed unless you run out of disk or something).
(Or if you want the notification regardless of the success status, change && to just ; -- I think this probably makes more sense.)
This is one of those rare Useful Uses of cat (although I still feel the urge to try to get rid of it!)
You can't put pipes in command variables:
$ foo='| cat'
$ echo bar $foo
bar | cat
The linked article explains how to do such things very well.
As mentioned in #l0b0's answer, the | will not be interpreted as you are hoping.
If you wanted to cut down on repetition, you could do something like this:
if [ $(make -j10 "$1" 2>&1 > log.txt) ]; then
[ "$1" ] && grep -i --color "error" log.txt
notify-send -u critical -t 7 "BUILD DONE"
fi
The inside of the test is common to both branches. Instead of using tee so that the output can be piped, you can just indirect the output to log.txt. If "$1" isn't empty, grep for any errors in log.txt. Either way, do the notify-send.

how to create the option for printing out statements vs executing them in a shell script

I'm looking for a way to create a switch for this bash script so that I have the option of either printing (echo) it to stdout or executing the command for debugging purposes. As you can see below, I am just doing this manually by commenting out one statement over the other to achieve this.
Code:
#!/usr/local/bin/bash
if [ $# != 2 ]; then
echo "Usage: testcurl.sh <localfile> <projectname>" >&2
echo "sample:testcurl.sh /share1/data/20110818.dat projectZ" >&2
exit 1
fi
echo /usr/bin/curl -c $PROXY --certkey $CERT --header "Test:'${AUTH}'" -T $localfile $fsProxyURL
#/usr/bin/curl -c $PROXY --certkey $CERT --header "Test:'${AUTH}'" -T $localfile $fsProxyURL
I'm simply looking for an elegant/better way to create like a switch from the command line. Print or execute.
One possible trick, though it will only work for simple commands (e.g., no pipes or redirection (a)) is to use a prefix variable like:
pax> cat qq.sh
${PAXPREFIX} ls /tmp
${PAXPREFIX} printf "%05d\n" 72
${PAXPREFIX} echo 3
What this will do is to insert you specific variable (PAXPREFIX in this case) before the commands. If the variable is empty, it will not affect the command, as follows:
pax> ./qq.sh
my_porn.gz copy_of_the_internet.gz
00072
3
However, if it's set to echo, it will prefix each line with that echo string.
pax> PAXPREFIX=echo ./qq.sh
ls /tmp
printf %05d\n 72
echo 3
(a) The reason why it will only work for simple commands can be seen if you have something like:
${PAXPREFIX} ls -1 | tr '[a-z]' '[A-Z]'
When PAXPREFIX is empty, it will simply give you the list of your filenames in uppercase. When it's set to echo, it will result in:
echo ls -1 | tr '[a-z]' '[A-Z]'
giving:
LS -1
(not quite what you'd expect).
In fact, you can see a problem with even the simple case above, where %05d\n is no longer surrounded by quotes.
If you want a more robust solution, I'd opt for:
if [[ ${PAXDEBUG:-0} -eq 1 ]] ; then
echo /usr/bin/curl -c $PROXY --certkey $CERT --header ...
else
/usr/bin/curl -c $PROXY --certkey $CERT --header ...
fi
and use PAXDEBUG=1 myscript.sh to run it in debug mode. This is similar to what you have now but with the advantage that you don't need to edit the file to switch between normal and debug modes.
For debugging output from the shell itself, you can run it with bash -x or put set -x in your script to turn it on at a specific point (and, of course, turn it off with set +x).
#!/usr/local/bin/bash
if [[ "$1" == "--dryrun" ]]; then
echoquoted() {
printf "%q " "$#"
echo
}
maybeecho=echoquoted
shift
else
maybeecho=""
fi
if [ $# != 2 ]; then
echo "Usage: testcurl.sh <localfile> <projectname>" >&2
echo "sample:testcurl.sh /share1/data/20110818.dat projectZ" >&2
exit 1
fi
$maybeecho /usr/bin/curl "$1" -o "$2"
Try something like this:
show=echo
$show /usr/bin/curl ...
Then set/unset $show accordingly.
This does not directly answer your specific question, but I guess you're trying to see what command gets executed for debugging. If you replace #!/usr/local/bin/bash with #!/usr/local/bin/bash -x bash will run and echo the commands in your script.
I do not know of a way for "print vs execute" but I know of a way for "print and execute", and it is using "bash -x". See this link for example.

Check if a program exists in bash

I am trying to check if md5sum or digest exists on solaris and script is used on different machines.
Here is the function in sh script which is called from a ksh script
getMD5cmd ()
{
PATH="${PATH}:/bin:/usr/bin:/usr/sfw/bin:/usr/local/bin:/usr/sbin/bin"
if type -p md5sum;then
MD5CMD=`type -p md5sum`
elif type -p digest;then
MD5CMD="`type -p digest` -a md5"
fi
echo "HERE ${MD5CMD}"
}
When I run scripts I get
-p not found
md5sum not found
-p not found
digest is /bin/digest
HERE
However, when I type it in a terminal, works as exptected
Any Ideas?
Thanks
You are likely running ksh or possibly Bash for your interactive shell. Both of these have a -p option for type. The shell (probably sh) that your script is running in has type but doesn't have the -p option so it's looking for "-p" as the name of an executable and it doesn't find it.
So you could change your script to use ksh or you could use the which program. The latter is probably more portable, since some systems don't have ksh.
As you are setting the PATH, knowing where precisely the command is seems unnecessary.
getMD5cmd ()
{
PATH=${PATH}:/bin:/usr/bin:/usr/sfw/bin:/usr/local/bin:/usr/sbin/bin
md5sum /dev/null >/dev/null 2>&1 && MD5CMD=md5sum || MD5CMD="digest -a md5"
echo "HERE ${MD5CMD}"
}
getMD5cmd
Have you tried the following syntax:
MD5CMD="$(type -p md5sum digest |sed -e 's/digest$/digest -a md5/' |head -1)"
if [ -z "$MD5CMD" ]; then
echo 'no md5 sum command found' >&2
exit 1
fi
echo "HERE $MD5CMD"
I tried this in Cygwin and type will return multiple rows, so it works.
if which md5sum >/dev/null 2>&1; then
md5cmd="md5sum"
elif which digest >/dev/null 2>&1; then
md5cmd="digest -a md5"
else
echo "No md5 command found" >&2
exit 1
fi
$md5cmd YOUR_FILE

Resources