bash script: if use with (conditon) or [condition] - linux

I have a subtle question about if statement in bash script: what type of parenthesis should follow "if"?
I have two statements both working right now, but not working if I exchange () and [].
1.compare variable name
NAME="dada"
if [ "$NAME" == "dada" ]; then SOME COMMANDS; fi
If change [] to (), error:
dada: command not found
2.using grep
if ( grep -q "lalala" mytest.txt ); then SOME COMMANDS; fi
If change () to [], error:
[: lalala: binary operator expected
I read the linux man, which suggest using []. So is it because of using grep? Can we say generally if using another program inside if, we should use ()?
A side question about double quotation marks,
is it necessary to quote text in shell? for example, echo "$NAME" or NAME="fadfaj"?
I got confused because when I try to use $NAME plus some special characters such as "_" or space, where I need to use "\", I got different behaviour with and without "".
NAME="myfolder"
echo $NAME\_na
echo "$NAME\_na"
output
myfolder_na
myfolder\_na
I understand these are naive questions but I do appreciate all your help!

The thing following the if keyword is a command. The command is executed, and the condition is true if the command succeeds (exits with a status of 0), false if it fails.
Though it looks like shell syntax, the [ symbol is actually the name of a command (which happens to be built into the shell). For example, you could type just
[ "$NAME" = "dada" ]
at a shell prompt, with no if, and it would evaluate the same condition and set $?. You can also do this:
[ "$NAME" = "dada" ] && echo Yes
The ] is a required last argument to the [ command. Apart from that requirement, the [ command is equivalent to the test command:
if test "$Name" = "data" ; then echo yes ; fi
Note that I've used = rather than ==. The built-in test/[ command in bash recognizes either form, but = is the original syntax, and some implementations don't recognize ==.
[ is the most common command to use with if, but you can use any command, including `grep:
if grep -q "lalala" mytest.txt; then SOME COMMANDS; fi
The parentheses are unnecessary. (If you add them, you're using a compound command rather than a simple command; there's no need to do so.)
See the bash manual (info bash) for more information. Search for
`test'
(with the backtick and apostrophe) for information on the built-in test command.
Bash also has a [[ command which has some advantages over the older [.

Related

What would cause the BASH error "[: too many arguments" after taking measures for special characters in strings?

I'm writing a simple script to check some repositories updates and, if needed, I'm making new packages from these updates to install new versions of those programs it refers to (in Arch Linux). So I made some testing before executing the real script.
The problem is that I'm getting the error [: excessive number of arguments (but I think the proper translation would be [: too many arguments) from this piece of code:
# Won't work despite the double quoted $r
if [ "$r" == *"irt"* ]; then
echo "TEST"
fi
The code is fixed by adding double square brackets which I did thanks to this SO answer made by #user568458:
# Makes the code works
if [[ "$r" == *"irt"* ]]; then
echo "TEST"
fi
Note that $r is defined by:
# Double quotes should fix it, right? Those special characters/multi-lines
r="$(ls)"
Also note that everything is inside a loop and the loop progress with success. The problems occurs every time the if comparison matches, not printing the "TEST" issued, jumping straight to the next iteration of the loop (no problem: no code exists after this if).
My question is: why would the error happens every time the string matches? By my understanding, the double quotes would suffice to fix it. Also, If I count on double square brackets to fix it, some shells won't recognize it (refers to the answer mentioned above). What's the alternative?
Shell scripting seems a whole new programming paradigm.. I never quite grasp the details and fail to secure a great source for that.
The single bracket is a shell builtin, as opposed to the double bracket which is a shell keyword. The difference is that a builtin behaves like a command: word splitting, file pattern matching, etc. occur when the shell parses the command. If you have files that match the pattern *irt*, say file1irt.txt and file2irt.txt, then when the shell parses the command
[ "$r" = *irt* ]
it expands $r, matches all files matching the pattern *irt*, and eventually sees the command:
[ expansion_of_r = file1irt.txt file2irt.txt ]
which yields an error. No quotes can fix that. In fact, the single bracket form can't handle pattern matching at all.
On the other hand, the double brackets are not handled like commands; Bash will not perform any word splitting nor file pattern matching, so it really sees
[[ "expansion_of_r" = *irt* ]]
In this case, the right hand side is a pattern, so Bash tests whether the left hand side matches that pattern.
For a portable alternative, you can use:
case "$r" in
(*irt*) echo "TEST" ;;
esac
But now you have a horrible anti-pattern here. You're doing:
r=$(ls)
if [[ "$r" = *irt* ]]; then
echo "TEST"
fi
What I understand is that you want to know whether there are files matching the pattern *irt* in the current directory. A portable possibility is:
for f in *irt*; do
if [ -e "$f" ]; then
echo "TEST"
break
fi
done
Since you're checking for files with a certain file name, I'd suggest to use find explicitly. Something like
r="$(find . -name '*irt*' 2> /dev/null)"
if [ ! -z "$r" ]; then
echo "found: $r"
fi

When should I use "" to quote a value in shell test and in echo?

I'm writing bash script like this:
VF_ETH=$(command)
if [ -n "$VF_ETH" ] ; then
echo "ixgbevf eth: "$VF_ETH
fi
command is a linux command, my question is:
$VF_ETH is to get value of VF_ETH, why use "" to quote it in line2 in shell test?
if I do not use "" to quote it, will test failed?
if use "" to quote a value is to make it into string, why not use in echo in line3?
Thank you
Assuming you get an actual command stored in VF_ETH variable, which contains spaces. Now if you use if [ -n $VF_ETH ] and when shell expands the variable, there will be multiple parameters to -n whereas it expects only one. Hence you might get something like binary operator expected error.
Also in the echo command, it is not mandatory to have only one parameter. Hence even if you are not using double quotes, it works.
Hence to avoid it, always use double quotes while expanding variables.
Also use https://www.shellcheck.net/ to check your script and it will give you correct information on where your script is wrong/not as per standard.
You should always double quote variables used in command line arguments and within [ ... ] tests, for example:
ls "$var"
echo "$var"
[ -f "$var" ]
test -f "$var"
In all the above examples the commands used will receive different values with and without the double quotes, when the value of $var starts with a bunch of spaces and contains some spaces. For example:
var=" an apple"
echo "$var" # prints " an apple"
echo $var # prints "an apple", without the leading space
You don't need to quote them in simple assignments, for example:
a=$var
a=$var$b
If the assignment contains spaces or other special characters, then you have to quote, or escape those special characters:
a="$var $b"
a=$var\ $b
a=$var" "$b
All the above are equivalent, but the first one is probably the easiest to read, and therefore recommended.
You don't need to quote special variables that never have unsafe values, for example:
test $? = 0
If you're unsure, or not yet confident, then a good role of thumb is to double quote always.
For 1. and 2. If you set $VF_ETH="x -a -z x" and test it with code:
if [ -n $VF_ETH ] ; then
echo yes
else
echo nope
fi
the output will be nope as the the inside of the square brackets would expand to -n x AND -z x (nonempty and empty). Adding quotes around the variable would fix that.
Set $VF_ETH="*". Now if you echo $foo bash would do wildcard expansion and echo would output the contents of your current directory.

Delete words from given files with sed

I have this assignment to solve:
"Write a shell script that continuously reads words from the keyboard and
deletes them from all the files given in the command line."
I've tried to solve it, here's my attempt:
#!/bin/bash
echo "Enter words"
while (true)
do
read wrd
if [ "$wrd" != "exit" ]
then
for i in $#
do
sed -i -e 's/$wrd//g' $i
done
else
break
fi
done
This is the error that I receive after introducing the command: ./h84a.sh fisier1.txt
Enter words
suc
sed: can't read 1: No such file or directory
Sorry if I'm not very specific, it's my first time posting in here. I'm working in a terminal on Linux Mint which is installed on another partition of my PC. Please help me with my problem. Thanks!
I think you can simplify your script quite a lot:
#!/bin/bash
echo "Enter words"
while read -r wrd
do
[ "$wrd" = exit ] && break
sed -i "s/$wrd//g" "$#"
done
Some key changes:
The double quotes around the sed command are essential, as shell variables are not expanded within single quotes
Instead of using a loop, it is possible to pass all of the file names to sed at once, using "$#"
read -r is almost always what you want to use
I would suggest that you take care with in-place editing using the -i switch. In some versions of sed, you can specify the suffix of a backup file like -i.bak, so the original file is not lost.
In case you're not familiar with the syntax [ "$wrd" = exit ] && break, it is functionally equivalent to:
if [ "$wrd" = exit ]
then break
fi
$# expands to the number of arguments (so 1 in this case)
You probably meant to use $* or "$#"

Bourne Shell Script test throws error with two of same file-type in folder?

I am writing a Bourne Script and am looking to select files that match certain regular expressions. I am doing this with an if test structure, and it identifies a file ending in ".o". However, when there are two files in a directory which I am searching that end in ".o" I get the following error: "expr: syntax error". How could this be possible?
if test "`expr \"$file\" : ${SPECIFIED_DIRECTORY}/*.o`" != "0"; then
do something
fi
A regular expression test is almost certainly the wrong tool for the job here, but let's assume that specified_directory (lower-case by convention to avoid conflicts with environment variables and builtins) contained a regex to match a directory name, as opposed to a literal name, and thus actually was the right tool. If that were the case, you'd want to write...
if expr "$file" : "$specified_directory"/'.*[.]o' >/dev/null; then
...
fi
No test command, no subshell. Keep it simple.
If you don't escape the . (in this case, by making it a character class, [.]), it has its normal regular-expression meaning, of matching exactly one character. Similarly, .* is the way to match zero-or-more of any character in a regex, not bare * (which is the fnmatch syntax).
This approach works on any POSIX shell, and calls no tools not available built-in to the shell, thus making it efficient at runtime.
contains_any_files() {
set -- "$1"/*
[ "$#" -gt 0 ] && [ -f "$1" ]
}
if contains_any_files "$dir"; then
...
fi
If I understand correctly, if there are any .o files in the directory, then do something:
if find "$dir"/*.o >/dev/null 2>&1; then
# do something
fi
The find command will exit with success only if there are any matching files. Otherwise it will exit with failure, and the then block won't be executed. The >/dev/null 2>&1 is to hide stdout and stderr.

bash double bracket issue

I'm very new to bash scripting and am running into an issue when using double brackets. I can't seem to get them to work at all in Ubuntu Server 11.10. My script below is in if_test.sh.
#!/bin/bash
if [[ "14"=="14" ]]; then
echo "FOO"
fi
When I run this simple shell script the output I get is: if_test.sh: 5: [[: not found
It seems that I'm running GNU bash version 4.2.10 after running bash --version from the terminal. Any help would be greatly appreciated. Thanks!
The problem lies in your script invocation. You're issuing:
$ sudo sh if_test.sh
On Ubuntu systems, /bin/sh is dash, not bash, and dash does not support the double bracket keyword (or didn't at the time of this posting, I haven't double-checked). You can solve your problem by explicitly invoking bash instead:
$ sudo bash if_test.sh
Alternatively, you can make your script executable and rely on the shebang line:
$ chmod +x if_test.sh
$ sudo ./if_test.sh
Also note that, when used between double square brackets, == is a pattern matching operator, not the equality operator. If you want to test for equality, you can either use -eq:
if [[ "14" -eq "14" ]]; then
echo "FOO"
fi
Or double parentheses:
if (( 14 == 14 )); then
echo "FOO"
fi
My answer doesn't apply to #lots_of_questions's question specifically, but you can also run into this problem if you have the wrong specifier at the top of your script:
#!/bin/sh
if [[ ... ]]
...
You should change that to
#!/bin/bash
if [[ ... ]]
...
Since you are new to scripting, you may be unaware that [[ is a bashism. You may not even know what a bashism is, but both answers given so far are leading you down a path towards a stunted scripting future by promoting their use.
To check if a variable matches a string in any flavor of Bourne shell, you can do test $V = 14 If you want to compare integers, use test $V -eq 14. The only difference is that the latter will generate an error if $V does not look like an integer. There are good reasons to quote the variable (test "$V" = 14), but the quotes are often unnecessary and I believe are the root cause of a common confusion, since "14"=="14" is identical to "14==14" where it is more obvious that '==' is not being used as an operator.
There are several things to note: use a single '=' instead of '==' because not all shells recognize '==', the [ command is identical to test but requires a final argument of ] and many sh coding guidelines recommend using test because it often generates more understandable code, [[ is only recognized by a limited number of shells (this is your primary problem, as your shell does not appear to recognize [[ and is looking for a command of that name. This is surprising if your shebang does indeed specify /bin/bash instead of /bin/sh).

Resources