What does the operator != mean in a shell script? - bash

In this i have an exclamation mark in my second if statement why is this used.
#!/bin/bash
name=$1
if [ "$name" = "" ]
then echo -n "Enter a name to search for: "
read name
fi
grep -i cheryl ~uli101/uli101/phonebook
grep -i $name ~uli101/uli101/phonebook
if [ "$?" != "0" ]
then echo -n "Name '$name' not in directory "
fi

The special shell parameter $? contains the exit code from the last command run. Every command you run from the shell reports a numeric status back to the shell when it finishes running; in general, a value of 0 means the command succeeded, and a nonzero value means it failed.
The grep command searches a file for lines matching a pattern. If it finds any matching lines, it prints them out, but it also exits with status 0 if it found at least one match, and a nonzero status if it didn't find any.
The syntax [ expression ] is a command that evaluates the given expression (usually a comparison of some sort) to see if it's true or not. Really, it's just another shell command, that exits with status 0 if the expression is true and 1 if it's false; the if construct in the shell decides what to do based on the value of $?.
And the != operator means 'is not equal to', so [ $? != 0 ] is checking to see if $? is not equal to zero.
Putting all that together, the above code checks to see if the grep found a match or not.
The origin of != is the C family of programming languages, in which the exclamation point generally means "not". In bash, a ! at the start of a command will invert the exit status of the command, turning nonzero values to zero and zeroes to one. So you could also "move the exclamation point" and rewrite the above expression like this:
if ! [ $? == 0 ]
However, since if itself operates based on exit status, all of the above code is doing extra work. You can skip the middleman and just test grep directly:
if ! grep -i "$name" ~uli101/uli101/phonebook; then
echo "Name '$name' not in directory."
fi
Note that I put double quotes around $name, which prevents any spaces in the value from separating it into multiple arguments to grep.

[ "$?" != "0" ] means "$? not equal to 0". See the full list of Bash test operators here.
Note that $? will be set to 0 if grep finds a match, and 1 otherwise.

Related

bash if "$1" == "0" is always false when running function for bash prompt

I have been struggling with this for a long time.
Trying to change colour as part of my prompt depending on the exit code of the last command.
I have reduced my prompt to a minimal example:
Red="\[\033[31m\]"
Green="\[\033[32m\]"
Reset="\[\033[0m\]"
statColour(){
if [[ "$1" == "0" ]]; then
echo -e "${Green} $1 "
else
echo -e "${Red} $1 "
fi
}
export PS1="$(statColour \$?)What Colour? $Reset"
And results in red always being used despite the fact the number is clearly 0 in the first instance.
I have tried [ and $1 -eq 0 with no success. Why isn't this working?
Try this:
Red="\033[35m"
Green="\033[32m"
Reset="\033[0m"
statColour(){
if [[ $1 = 0 ]]; then
echo -e "${Green} $1 "
else
echo -e "${Red} $1 "
fi
}
export PS1="\$(statColour \$?)What Colour? $Reset"
# ^
Color definitions changed
Call of statColour is now done every time, and not only once.
if [[ ]] optimized
For an explanation why you always take the false branch:
You are calling statColour with \$? as argument. The backslash ensures, that the $ is taken literally (and not as the start of a parameter expanson), so you have in effect the literal string $?. Since ? is a wildcard character, it is undergoing filename generation, i.e. the parameter is replaced by all files where the name is a $, followed by a single character. If there are no such files in your directory (which is probably the case), the string $? is passed literally to statColour.
Inside statColour, you wrote
[[ "$1" == "0" ]]
which means that you ask, whether the string $? is equal to the string 0. This is never the case, hence the comparision is always false.
For your problem, you could try this approach (not tested, so you may have to debug it a bit):
statColour() {
# Fetch the exit code of the last program
local last_exit_code=$?
if ((last_exit_code == 0)) # Numeric comparision
then
.....
else
...
fi
# Preserve the exit code
return $last_exit_code
}
and set the prompt as
PS1='$(statColour) '"$Reset"
The single quotes ensure that statColour is evaluated dynamically, while $Reset is in double quotes since it is OK to evaluate it statically.

How to use contents of text file as input to shell script?

I'm tasked with writing a shell script that takes in a string of 6 letters and numbers and checks if they meet a certain criteria.
This is that script
FILE=$1
var=${#FILE}
if [ $var -gt 6 ] || [ $var -lt 6 ]
then
#echo $FILE "is not a valid NSID"
exit 1
else if ( echo $1 | egrep -q '^[A-Za-z]{3}\d{3}$' )
then
#echo $1 "is a valid NSID"
exit 0
else
#echo $1 "is not a valid NSID"
exit 1
fi
fi
It works. so that isn't where the problem is.
What I am trying to do is use a "wrapper" script to gather potential valid NSID's from a text file and call that script on the list of inputs. so that if I call that script within my wrapper script it will step through the text file I have given my wrapper and check if each line is valid.
FILE=$1
YES= more $FILE
if ( exec /path/to/file/is_nsid.sh $YES -eq 0 )
then
echo $FILE "is a valid NSID"
else
echo $FILE "is not a valid NSID"
fi
so if I called it with a text file called file1.txt which contained
yes123
yess12
ye1243
it would output whether each was valid or not.
The line
YES= more $FILE
Sets YES in the environment passed to the command more $FILE. That's probably not what you intended.
The line
if ( exec /path/to/file/is_nsid.sh $YES -eq 0 )
starts a subshell to execute exec /path/to/file/is_nsid.sh $YES -eq 0. (That's what the parentheses do.) exec then replaces the subshell with a process which executes
/path/to/file/is_nsid.sh $YES -eq 0
which in turn runs the script at is_nsid.sh, passing it two or three command line arguments:
the value of $YES. This could be several arguments if the value of the shell variable includes whitespace or a glob symbol, but in this case it is more likely to be nothing since $YES has not been defined.
-eq
0
Since your script only examines its first argument, that's probably equivalent to
/path/to/file/is_nsid.sh -eq
That will, presumably, terminate with a failure status code, and since the subshell has been replaced with the script execution, that will also be the return status of the subshell. (Without exec, there would be essentially no difference; the subshell's return status is that of the last command executed in the subshell. Without either the parentheses or the exec, the result would also be the same. So you could have just written if /path/to/file/is_nsid.sh $YES -eq 0 and it would produce the same incorrect result.)
What you presumably wanted to do was to read each line in the file whose name is passed as the first command-line argument to the script. You could do that as follows:
while read -r line; do
if /path/to/file/is_nsid.sh "$line"; then
echo "$line is a valid NSID"
else
echo "$line is not a valid NSID"
fi
done < "$1"
You could simplify your is_nsid script considerably. The following is equivalent:
[ $#1 -eq 6 ] && echo "$1" | egrep -q '^[A-Za-z]{3}\d{3}$'
Note that \d is a Gnu extension to egrep and should not be relied on in portable code (which I assume this is trying to be). You should use [0-9] or [[:digit:]] instead.
The length check is actually unnecessary since the regex can only match six-character lines. Personally, I'd leave it out and just use
echo "$1" | egrep -q '^[[:alpha:]]{3}[[:digit:]]{3}$'
I removed all the unnecessary if statements. If I had left them in, I would have changed else if ... then ... fi to simply elif ... then ... to avoid unnecessary if nesting.

Why doesn't if [ echo $foo | grep -q bar ] work?

I'm trying to read user input and compare it against a stored value as follows:
read confirm
if [ echo $confirm | grep -q y ]; then
...
fi
However, this results in a pair of errors:
[: missing `]'
grep: ]: No such file or directory
Why does this happen, and what's the appropriate alternative?
Short Answer
For your immediate use case, you simply want:
if echo "$confirm" | grep -q y; then
...or its much more efficient equivalent (if your shell is bash):
if [[ $confirm = *y* ]]; then
...or its much more efficient equivalent (for any POSIX shell):
case $confirm in *y*) echo "Put your code for the yes branch here" ;; esac
Why was the original wrong?
[ is not part of if syntax: if simply takes a (potentially compound) command as its argument before the then. [ is different name for the test command, which runs checks on its arguments; however, if what you want to test is the exit status of grep -q, then the test command doesn't need to be invoked for this purpose at all.
If you put a | inside a [ command, that makes your compound command a pipeline, and starts a new simple command. Arguments after the | are thus no longer passed to [.
With your original code:
if [ echo $confirm | grep -q y ]; then
...this was running two commands, with a pipeline between them:
[ echo $confirm # first command
grep -q y ] # second command
Since [ requires that its last argument be ], it reported that that mandatory argument was missing; and since grep treats extra arguments as filenames to read, it complained that no file named ] could be found.
Also, [ "$foo" ] checks whether the contents of foo is nonempty. Since the output of grep -q is always empty, [ "$(echo "$confirm" | grep -q y)" ], while syntactically correct, would always evaluate to false, even while exit status of grep -q changes to indicate whether a match was found. ([ "$(echo "$confirm" | grep y)" ], by contrast, is an alternative that emits a correct result - using [ ] to test whether the output from grep is or is not empty -- but is much less efficient than the best-practice approaches).
Formal if syntax
From help if:
if: if COMMANDS; then COMMANDS; [ elif COMMANDS; then COMMANDS; ]... [ else COMMANDS; ] fi
The if COMMANDS list is executed. If its exit status is zero, then the
then COMMANDS list is executed. Otherwise, each elif COMMANDS list is
executed in turn, and if its exit status is zero, the corresponding
then COMMANDS list is executed and the if command completes. Otherwise,
the else COMMANDS list is executed, if present. The exit status of the
entire construct is the exit status of the last command executed, or zero
if no condition tested true.
Notably, if takes a list of COMMANDS, and no [ is included in the syntax specification.

Bash if statement not working properly

I have a bash statement to test a command line argument. If the argument passed to the script is "clean", then the script removes all .o files. Otherwise, it builds a program. However, not matter what is passed (if anything), the script still thinks that the argument "clean" is being passed.
#!/bin/bash
if test "`whoami`" != "root" ; then
echo "You must be logged in as root to build (for loopback mounting)"
echo "Enter 'su' or 'sudo bash' to switch to root"
exit
fi
ARG=$1
if [ $ARG == "clean" ] ; then
echo ">>> cleaning up object files..."
rm -r src/*.o
echo ">>> done. "
echo ">>> Press enter to continue..."
read
else
#Builds program
fi
Answer for first version of question
In bash, spaces are important. Replace:
[ $ARG=="clean" ]
With:
[ "$ARG" = "clean" ]
bash interprets $ARG=="clean" as a single-string. If a single-string is placed in a test statement, test returns false if the string is empty and true if it is non-empty. $ARG=="clean" will never be empty. Thus [ $ARG=="clean" ] will always return true.
Second, $ARG should be quoted. Otherwise, if it is empty, then the statement reduces to `[ == "clean" ] which is an error ("unary operator expected").
Third, it is best practices to use lower or mixed case for your local variables. The system uses upper-case shell variables and you don't want to accidentally overwrite one of them.
Lastly, with [...], the POSIX operator for equal, in the string sense, is =. Bash will accept either = or == but = is more portable.
first:
Every string must double quoted or will error absent argument.
second:
for string used only = or != not a == and also -n and -z commands.
third:
you may combine conditions by -a and -o commands but newer used enclose in () yous conditions so not to get error. Logical operators acts through operators presidence, fist calculate -o operator and then -a! For example
[ -n "$1" -a $1 = '-h' -o $1 = '--help' ] && { usage; exit 0; }
will work when passed to script at least 1 argument and is -h or --help. All spaces must be!!! Bush do short cycle logical evaluations. So don't trouble for case when $1 don't exist in second condition because of result of this expression is determined in first one. next don't calculate in this case. But if your argument may contains space symbols you need it double quote. You must do it also in command line too! Else you get error in script or split your arguments in two or more parts.
Operator == isn't used in test. For numbers(not siring) used -eq or -ne commands. See man 1 test for full descriptions. test EXPRESSION... equivalent of [ EXPRESSIONS... ]. More shirt form of test.

How do I use a file grep comparison inside a bash if/else statement?

When our server comes up we need to check a file to see how the server is configured.
We want to search for the following string inside our /etc/aws/hosts.conf file:
MYSQL_ROLE=master
Then, we want to test whether that string exists and use an if/else statement to run one of two options depending on whether the string exists or not.
What is the BASH syntax for the if statement?
if [ ????? ]; then
#do one thing
else
#do another thing
fi
From grep --help, but also see man grep:
Exit status is 0 if any line was selected, 1 otherwise;
if any error occurs and -q was not given, the exit status is 2.
if grep --quiet MYSQL_ROLE=master /etc/aws/hosts.conf; then
echo exists
else
echo not found
fi
You may want to use a more specific regex, such as ^MYSQL_ROLE=master$, to avoid that string in comments, names that merely start with "master", etc.
This works because the if takes a command and runs it, and uses the return value of that command to decide how to proceed, with zero meaning true and non-zero meaning false—the same as how other return codes are interpreted by the shell, and the opposite of a language like C.
if takes a command and checks its return value. [ is just a command.
if grep -q ...
then
....
else
....
fi
Note that, for PIPE being any command or sequence of commands, then:
if PIPE ; then
# do one thing if PIPE returned with zero status ($?=0)
else
# do another thing if PIPE returned with non-zero status ($?!=0), e.g. error
fi
For the record, [ expr ] is a shell builtin† shorthand for test expr.
Since grep returns with status 0 in case of a match, and non-zero status in case of no matches, you can use:
if grep -lq '^MYSQL_ROLE=master' ; then
# do one thing
else
# do another thing
fi
Note the use of -l which only cares about the file having at least one match (so that grep returns as soon as it finds one match, without needlessly continuing to parse the input file.)
†on some platforms [ expr ] is not a builtin, but an actual executable /bin/[ (whose last argument will be ]), which is why [ expr ] should contain blanks around the square brackets, and why it must be followed by one of the command list separators (;, &&, ||, |, &, newline)
just use bash
while read -r line
do
case "$line" in
*MYSQL_ROLE=master*)
echo "do your stuff";;
*) echo "doesn't exist";;
esac
done <"/etc/aws/hosts.conf"
Below code sample should work:
(echo "hello there" | grep -q "AAA") && [ $? -eq 0 ] && echo "hi" || echo "bye"

Resources