Bash string comparison of two identical strings false? - macos

Hello I have the following script:
#! /bin/bash
Output=$(defaults read com.apple.systemuiserver menuExtras | grep Bluetooth.menu)
Check="\"/System/Library/CoreServices/Menu Extras/Bluetooth.menu\","
echo $Output
echo $Check
if [ "$Output" = "$Check" ]
then
echo "OK"
else
echo "FALSE"
echo "Security Compliance Setting 'Show Bluetooth Status in Menu Bar(2.1.3)' has been changed!" | logger
fi
When you run it, both variables have the exact same output however the check always says it is FALSE
here is the out put from my terminal:
"/System/Library/CoreServices/Menu Extras/Bluetooth.menu",
"/System/Library/CoreServices/Menu Extras/Bluetooth.menu",
FALSE
Any idea why it is not detecting that they are the same?

As everyone in the comments suspected, the problem is whitespace in $Output (which echo $Output removes); specifically, 4 leading spaces (note that in the following, "$ " is my shell prompt):
$ defaults read com.apple.systemuiserver menuExtras | grep Bluetooth.menu
"/System/Library/CoreServices/Menu Extras/Bluetooth.menu",
$ Output=$(defaults read com.apple.systemuiserver menuExtras | grep Bluetooth.menu)
$ echo $Output
"/System/Library/CoreServices/Menu Extras/Bluetooth.menu",
$ echo "[$Output]"
[ "/System/Library/CoreServices/Menu Extras/Bluetooth.menu",]
$ Check=" \"/System/Library/CoreServices/Menu Extras/Bluetooth.menu\","
$ if [ "$Output" = "$Check" ]; then echo "OK"; else echo "FALSE"; fi
OK
Note that since the number of spaces might not always be the same, it might be safer to use bash's wildcard matching capability within a [[ ]] conditional expression (this will not work with [ ]):
$ Check="\"/System/Library/CoreServices/Menu Extras/Bluetooth.menu\","
$ if [[ "$Output" = *"$Check" ]]; then echo "OK"; else echo "FALSE"; fi
OK
You could also skip the string comparison entirely, and just use the fact that grep returns a success status only if it finds a match:
#!/bin/bash
if defaults read com.apple.systemuiserver menuExtras | grep -q "/System/Library/CoreServices/Menu Extras/Bluetooth.menu"; then
echo "OK"
else
echo "FALSE"
echo "Security Compliance Setting 'Show Bluetooth Status in Menu Bar(2.1.3)' has been changed!" | logger
fi

Related

Perform action if nothing is returned

I'm trying to echo "It was not found" if the netstat returns no result. But, if it does return a result, then to display the netstat results.
I'm trying to google for what I'm using and can't find much about it.
#!/bin/bash
echo "Which process would you like to run netstat against?"
read psname
echo "Looking for '$psname'"
sleep 2
command=$(netstat -tulpn | grep -e $psname)
[[ ! -z $command ]]
It's to do with the [[ ! -z $command ]]
[[ ! -z $command ]] doesn't 'show' you any output.
Use a if/else setup to show the result;
if [[ ! -z $command ]]; then
echo "$psname was found!"
else
echo "No process named '${psname}' found!"
fi
Or a shorthand variant;
[[ ! -z $command ]] && echo "$psname was found!" || echo "No process named '${psname}' found!"
Note if the user input may contain a space, it's saver to use "" around the grep string;
command=$(netstat -tulpn | grep -e "$psname")
When to wrap quotes around a shell variable?
Do not intercept the output directly. You can instead do something like:
if ! netstat -tulpn | grep -e "$psname"; then
echo "not found" >&2
fi
If grep matches any output, it will print it to stdout. If it mathches nothing, it returns non-zero and the shell will write a message to stderr.

Setting a variable inside a conditional

Is it possible to set a variable from the output of a command inside of a conditional where the conditional is false if nothing gets assigned to the variable.
If I set the variable to a grep with no return and then test:
test=$(echo hello | grep 'helo')
if [[ ! -z $test ]]; then
echo "is set"
else
echo "not set"
fi
Output: not set (this is expected)
But I'm trying to put it all into one statement like this:
test=
if [[ ! -z test=$(echo hello | grep 'helo') ]]; then
echo "is set"
else
echo "not set"
fi
output: "is set" (expected not set)
grep returns success if there is a match, so you can just do:
if test=$(echo hello | grep 'helo')
then
echo "Match: $test"
else
echo "No match"
fi
If you're running something that doesn't differentiate by exit code, you can assign and check in two statements on the same line:
if var=$(cat) && [[ -n $var ]]
then
echo "You successfully piped in some data."
else
echo "Error or eof without data on stdin."
fi
(or ; instead of && if you want to inspect the result even when the command reports failure)
Bit of a hack, using the shell's parameter expansion alternate value syntax, echo -e and some backspaces:
test=$(echo hello | grep 'helo'); echo -e not${test:+\\b\\b\\bis} set
Which outputs is set or not set depending on what grep finds.

Conditional statement bash script

I need help with replacing the following script with a different format where a configuration file, and a loop is used.
[FedoraC]$ cat script.sh
#!/bin/bash
grep -q /tmp /etc/fstab
if [ $? -eq 0 ]; then
echo "True"
else
echo "False"
fi
mount | grep ' /tmp' | grep nodev
if [ $? -eq 0 ]; then
echo "True"
else
echo "False"
fi
mount | grep /tmp | grep nosuid
if [ $? -eq 0 ]; then
echo "True"
else
echo "False"
fi
So far I have the following script which should take the values from a source/conf file and run each command found in the conf file one by one. After the command is executed the output would be "True" or "False"
conf file is formed by Unix commands: /opt/conf1
[FedoraC]$ cat conf1
grep -q /tmp /etc/fstab
mount | grep /tmp | grep nodev
mount | grep /tmp | grep nosuid
mount | grep /tmp | grep noexec
[FedoraC]$ cat new_script.sh
#!/bin/bash
. conf1
for i in $#;
do $i
if [ $i -eq 0 ]; then
echo "Passed"
else
echo "Failed"
fi
done
Instead of displaying the output based on the conditional statement, the script runs each line one by one from conf1, and not echo messages are seen.
Can I get some help please.
try this:
#! bin/bash
while read L; do
echo $L'; exit $?'|sh
if [ $? -eq 0 ]; then
echo Pass
else
echo Failed
fi
done < conf1
The more robust and canonical way to do this would be to have a directory /opt/conf1.d/, and put each of your lines as an executable script in this directory. You can then do
for file in /opt/conf1.d/*
do
[[ -x $file ]] || continue
if "$file"
then
echo "Passed"
else
echo "Failed"
fi
done
This has the advantages of supporting multi-line scripts, or scripts with more complex logic. It also lets you write the check script in any language, and lets scripts and packages add and remove contents easily and non-interactively.
If you really want to stick with your design, you can do it with:
while IFS= read -r line
do
if ( eval "$line" )
then
echo "Passed"
else
echo "Failed"
fi
done < /opt/conf1
The parentheses in the if statement runs eval in a subshell, so that lines can't interfere with each other by setting variables or exiting your entire loop.

Running command within test statement in bash

I have a piece of code to find the first file in a directory:
bash~> $( echo eval "ls | head -1" )
arguprog.sh
This snippet was then added to an if statement, to run a different set of commands if that file was arguprog.sh:
bash~> if [[ $( echo eval "ls | head -1" ) == "arguprog.sh" ]]; then echo "TRUE"; else echo "FALSE"; fi;
FALSE
However this is not doing what I want. It returns FALSE even though the first file is arguprog.sh!
Is there a way to resolve this while still doing the string comparison entirely within the test block?
First, eval is evil, especially when it's not needed. In your case, eval is not needed!
Replace the coding horror you showed with just:
ls | head -1
and to include it in your test statement:
if [[ $(ls | head -1) = "arguprog.sh" ]]; then echo "TRUE"; else echo "FALSE"; fi
But this is wrong and broken (see below).
Now something more general: do not parse the output of ls. If you want to find the first file (or directory or...) in your current dir, use globs and this method:
shopt -s nullglob
files=( * )
# The array files contains the names of all the files (and directories...)
# in the current directory, sorted by name.
# The first one is given by the expansion of "${files[0]}". So:
if [[ "${files[0]}" = "arguprog.sh" ]]; then echo "TRUE"; else echo "FALSE"; fi
Notice that your method, parsing ls is wrong. Look:
$ # Create a new scratch dir
$ mkdir myscratchdir
$ # Go in there
$ cd myscratchdir
$ # touch a few files:
$ touch $'arguprog.sh\nwith a newline' "some other file"
$ # I created 2 files, none of them is exactly arguprog.sh. Now look:
$ if [[ $(ls | head -1) = "arguprog.sh" ]]; then echo "TRUE"; else echo "FALSE"; fi
TRUE
$ # HORROR!
There are twisted work-arounds for this, but really, the best method is the one I just gave you.
Done!
The value of $( echo eval "ls | head -1" ) is "eval ls | head -1" not "arguprog.sh", hence why you get FALSE.
Take a look at this:
$ a=$( echo eval "ls | head -1" )
$ echo $a
eval ls | head -1
The reason you see arguprog.sh when you run bash~> $( echo eval "ls | head -1" ) is because bash executes the command which is eval ls | head -1 and returns the result which is arguprog.sh.
In order to do the same in your if-statement, you need to execute it as well by enclosing it in another set of $(...), like this:
$ if [[ $($( echo eval "ls | head -1" )) == "arguprog.sh" ]]; then echo "TRUE"; else echo "FALSE"; fi;
TRUE
But don't do that! It is a lot easier to simply use:
$ if [[ $(ls | head -1) == "arguprog.sh" ]]; then echo "TRUE"; else echo "FALSE"; fi;
TRUE
Also note that parsing the output of ls is not good practice. See ParsingLs for details.
Try this way:
if [[ `ls | head -1` = "04 dynamic-programming.pdf" ]]; then
echo "TRUE"
else
echo "FALSE"
fi
You should merely use = instead of == and skip the eval part.

How to check if a file contains a specific string using Bash

I want to check if a file contains a specific string or not in bash. I used this script, but it doesn't work:
if [[ 'grep 'SomeString' $File' ]];then
# Some Actions
fi
What's wrong in my code?
if grep -q SomeString "$File"; then
Some Actions # SomeString was found
fi
You don't need [[ ]] here. Just run the command directly. Add -q option when you don't need the string displayed when it was found.
The grep command returns 0 or 1 in the exit code depending on
the result of search. 0 if something was found; 1 otherwise.
$ echo hello | grep hi ; echo $?
1
$ echo hello | grep he ; echo $?
hello
0
$ echo hello | grep -q he ; echo $?
0
You can specify commands as an condition of if. If the command returns 0 in its exitcode that means that the condition is true; otherwise false.
$ if /bin/true; then echo that is true; fi
that is true
$ if /bin/false; then echo that is true; fi
$
As you can see you run here the programs directly. No additional [] or [[]].
In case if you want to check whether file does not contain a specific string, you can do it as follows.
if ! grep -q SomeString "$File"; then
Some Actions # SomeString was not found
fi
In addition to other answers, which told you how to do what you wanted, I try to explain what was wrong (which is what you wanted.
In Bash, if is to be followed with a command. If the exit code of this command is equal to 0, then the then part is executed, else the else part if any is executed.
You can do that with any command as explained in other answers: if /bin/true; then ...; fi
[[ is an internal bash command dedicated to some tests, like file existence, variable comparisons. Similarly [ is an external command (it is located typically in /usr/bin/[) that performs roughly the same tests but needs ] as a final argument, which is why ] must be padded with a space on the left, which is not the case with ]].
Here you needn't [[ nor [.
Another thing is the way you quote things. In bash, there is only one case where pairs of quotes do nest, it is "$(command "argument")". But in 'grep 'SomeString' $File' you have only one word, because 'grep ' is a quoted unit, which is concatenated with SomeString and then again concatenated with ' $File'. The variable $File is not even replaced with its value because of the use of single quotes. The proper way to do that is grep 'SomeString' "$File".
Shortest (correct) version:
grep -q "something" file; [ $? -eq 0 ] && echo "yes" || echo "no"
can be also written as
grep -q "something" file; test $? -eq 0 && echo "yes" || echo "no"
but you dont need to explicitly test it in this case, so the same with:
grep -q "something" file && echo "yes" || echo "no"
##To check for a particular string in a file
cd PATH_TO_YOUR_DIRECTORY #Changing directory to your working directory
File=YOUR_FILENAME
if grep -q STRING_YOU_ARE_CHECKING_FOR "$File"; ##note the space after the string you are searching for
then
echo "Hooray!!It's available"
else
echo "Oops!!Not available"
fi
grep -q [PATTERN] [FILE] && echo $?
The exit status is 0 (true) if the pattern was found; otherwise blankstring.
if grep -q [string] [filename]
then
[whatever action]
fi
Example
if grep -q 'my cat is in a tree' /tmp/cat.txt
then
mkdir cat
fi
In case you want to checkif the string matches the whole line and if it is a fixed string, You can do it this way
grep -Fxq [String] [filePath]
example
searchString="Hello World"
file="./test.log"
if grep -Fxq "$searchString" $file
then
echo "String found in $file"
else
echo "String not found in $file"
fi
From the man file:
-F, --fixed-strings
Interpret PATTERN as a list of fixed strings, separated by newlines, any of
which is to be matched.
(-F is specified by POSIX.)
-x, --line-regexp
Select only those matches that exactly match the whole line. (-x is specified by
POSIX.)
-q, --quiet, --silent
Quiet; do not write anything to standard output. Exit immediately with zero
status if any match is
found, even if an error was detected. Also see the -s or --no-messages
option. (-q is specified by
POSIX.)
Try this:
if [[ $(grep "SomeString" $File) ]] ; then
echo "Found"
else
echo "Not Found"
fi
I done this, seems to work fine
if grep $SearchTerm $FileToSearch; then
echo "$SearchTerm found OK"
else
echo "$SearchTerm not found"
fi
grep -q "something" file
[[ !? -eq 0 ]] && echo "yes" || echo "no"

Resources