If statement in shell - bash

I'm making a script that executes a file a certain amount of times, then if the file executes properly, it generates another file, and this last file I need to store it in a directory. Example: I execute it like this: ./shell 5 DIR
Then, the crpit shell executes another file called simulation 5 times and it creates a directory and an output file for each time the file simulation was executed correctly.
The thing is, I have to put an if statement for the parameters they send when they execute the file, and I don't know how to, here's the code I have:
#!/bin/bash
if [ $# == 3 || $# == 2 ]; then
c=0
i=0
e=0
cont=0
while [ $c -le $0 ]
do
./simula cont RES
e = $?
if[ e == 0 ]; then
if[ $# == 3 ]; then
chmod RES $2
mkdir $1$c
mv RES $1$c/.
(( c++ ))
else
(( i++ ))
(( cont++ ))
done
echo Shan generat $c simulacions correctes.
echo Hi ha hagut $i simulacions erronies.
else
echo Nombre de parametres incorrecte: $#.
fi
So I must have either 2 or 3 paremeters when executing the file called shell, and thats why i have that if in the second line of code, however it keeps giving me an error:
./shell: line 12: syntax error near unexpected token `then'
./shell: line 12: ` if[ e == 0 ]; then'
I'm new to scripts and I have no idea what am I doing wrong, any clues?

Spacing is very important in shell scripts, it's not like programming languages where
if(x>y) and if ( x > y ) are the same thing.
In particular, if and [ are two different commands:
if[ e == 0 ] needs to be if [ e == 0 ]
More precisely, if is a shell keyword as are then, else, and fi
[ is either a shell builtin or an external program (or both) depending on your shell and is an alias for test. You can find out more with the which and type commands.
$ type [
[ is a shell builtin
$ which [
/bin/[
$ type fi
fi is a shell keyword
$ which fi
$

Does it works if you try if [ $e == 0 ]; instead of if[ e == 0 ];

When assigning variables, Bash doesn't allow leaving spaces in between the = sign like is common practice in other languages.
There must be a space separating reserved words like if.
So if [ $# == 3 ] is valid where if[ $# == 3 ] is not.
In Bash, it's important to use $ in front of variable names when you're calling the variable after it's been assigned.
So [ e == 0 ] would test to see if the literal string e is the same as the string 0. To test if the string assigned to variable e is the same as the string 0, use [ $e == 0 ] .
When testing integers, you can use -eq instead of ==. See man test for more information.
Also, $0 expands to the name of the script file, so you're while loop will never end if the script name is not 0 as the script name will not change while your script is running.

Related

expr and if in bash

What's wrong with this code? If I input the number 25, it output failed instead of lol. Am I missing something?
read -p "Enter number : " num
if [ `expr $num > 5` ]
then
echo "lol"
else
echo "failed"
fi
The code
if [ `expr $num > 5` ]
actually does not do want you think. It will run expr $num > 5, so evaluate parameter and redirect the out to a file named 5 ("put 25 in a file named 5") and if will evaluate the return code of the previous expression.
If the code if meant to check evaluate if a number is bigger than 5, replace
if [ `expr $num > 5` ]
with
[ "$num" -gt 5 ]
-gt stands for greater than
#babtistemm's answer gives you the suggested solution but in case you insist on using (the bit oldish) expr for some reason:
read -p "Enter number : " num
if expr "$num" '>' 5 >/dev/null
then
echo "lol"
else
echo "failed"
fi
Notes:
You need to quote > so that the shell does not interpret it as redirecting the stdout. You could also use \>.
It is good practice to add double quotes to $num as well, so that expr will interpret it as one expression, thus limiting the chances of a very bad bug or a malicious user hacking your program. (Best would be to do a sanity-check on $num before using it, e.g. checking if it is an integer.)
This solution necessitates calling a new process, expr, which costs a lot more resource from the OS than using the test shell command only.
If you omit the >/dev/null, you will also get a 0 or 1 printed (meaning false or true), the stdout of expr. But independently of that, expr sets its exit status, $? according to the result of the expression, which is tested then by the if. (A side remark: if you try to echo $? after calling expr, it may come at a surprise first that $? = 0 means true/success as exit status, and $? != 0 means false by convention.)
you can use an arithmetic expression in bash:
if (( num > 5 )); then ...
In the manual, see https://www.gnu.org/software/bash/manual/bash.html#Conditional-Constructs
Same in a short line:
read -p 'Enter a number: ' num
(( num > 5 )) && echo lol || echo fail
could be condensed:
read -p 'Enter a number: ' num;((num>5))&&echo lol||echo fail
This syntaxe will work if first command success!
read -p 'Enter a number: ' num
((num > 5)) && {
echo lol
/bin/wrongcommand
:
} || echo fail
Could output:
lol
bash: /bin/wrongcommand: No such file or directory
But no fail because of : (is an alias for true) will alway success.
You could of course group command for fail:
read -p 'Enter a number: ' num
((num > 5)) && {
echo lol
/bin/wrongcommand
:
} || {
echo fail
other command
}
Could be written:
read -p 'Enter a number: ' num;((num>5))&&{ echo lol;/bin/wrongcommand;:;}||{ echo fail;other command;}
You could group commands between {  and ;} (care about the space after first {!)
Enter a number: 4
fail
bash: other: command not found
Enter a number: 7
lol
bash: /bin/wrongcommand: No such file or directory

[0]-bash: [: 0*1%8: integer expression expected

Can someone point out what is wrong with the output.
for i in {0..127} ; do
echo -n [$i]
if [ $i*$j%8 -eq 0 ]; then
echo "\n"
fi
mytool -c "read 0x1540:0xa0:$i*$j"
done
I am trying to format the output into rows containing 8 items each.
I tried the suggestion below and modified my code to
for i in {0..8} ; for j in {0..16}; do
echo -n [$i*$j]
if [[ $i*$j%8 == 0 ]]; then
echo
fi
mytool -c "read 0x1540:0xa0:$i*$j"
done
Above with for i in {0..8} ; for j in {0..16}
I am expecting this to be a nested for loop.I am not very sure if this is how I do a nested loop in bash.
Still the output is not as I expect it.
My output looks like
[0]0x3
[1]0x4
[2]0x21
[3]0x1
[4]0x0
[5]0x0
[6]0x4
[7]0x41
[8]0x84
[9]0x80
[10]0x0
[11]0x0
[12]0x3
[13]0x0
[14]0x43
[15]0x49
[16]0x53
[17]0x43
[18]0x4f
[19]0x2d
[20]0x49
[21]0x4e
[22]0x43
[23]0x20
[24]0x0
[25]0x0
[26]0x9
[27]0x3a
[28]0x37
[29]0x34
[30]0x39
[31]0x34
I want [0] to [7] in ROW1
[8] to [15] in ROW2
and so on.
Use (( )) if you want to do math.
if ((i * j % 8 == 0)); then
Given your problem description I suggest a bit of a rewrite.
for i in {0..15}; do
for j in {0..7}; do
echo -n "[$((i * 8 + j))]"
mytool -c "read 0x1540:0xa0:$i*$j"
done
echo
done
The test command ([ is an alias for test, not syntax) requires the expression to be built up from multiple arguments. This means spaces are critical to separate operators and operands. Since each part is a separate argument, you also need to quote the * so that the shell does not expand it as a file glob prior to calling test/[.
if [ "$i" "*" "$j" % 8 -eq 0 ]; then
The test command expects 7 separate arguments here: $i, *, $j, %, -eq, and 0, which it then assembles into an expression to evaluate. It will not parse an arbitrary string into an expression.
As noted by John Kugelman, there are easier ways to accomplish such arithmetic in bash.

Using selfwritten .sh file in another .sh file

I am writing a small .sh program in bash.
The problem is extremely simple, i.e, to dind the primefactors of a number.
What I've done is written a .sh file to check if a number is prime or not.
Here is the code for that :
if [ $# -ne 1 ]; then
exit
fi
number=$1
half=$(($number / 2))
for (( i=2;i<$half;i++ ))
do
rem=$(($number % $i))
if [ $rem -eq 0 ]; then
echo "0"
exit
fi
done
echo "1"
And the second .sh file to generate prime factors :
clear
echo "Enter number : "
read number
half=$(($number / 2))
for(( i=1;i<=$half;i++ ))
do
rem=$(($number % $i))
if [ $rem -eq 0 ]; then
ok=`prime.sh $rem`
if [ "$ok" == "1" ]; then
echi $i
fi
fi
done
This line ,
ok=`prime.sh $rem`
gives the following error :
primefactor.sh: line 10: prime.sh: command not found
So, is it not possible to divide a program into smaller modules and use it in the other modules like other programming languages ?
Some help on how to achieve this will be helpful.
primefactor.sh: line 10: prime.sh: command not found
...means that prime.sh is not in your PATH, or is not executable. There are a few ways you can remedy this:
First, ensure that the +x bit is set:
chmod +x prime.sh
...then, add it to your PATH:
PATH=.:$PWD
...or invoke it directly:
ok=$(./prime.sh)
By the way, names ending in .sh are appropriate for POSIX sh libraries, not bash scripts (which typically aren't valid POSIX sh scripts anyhow). You don't run ls.elf; you should run prime, not prime.sh, for the same reasons.
That said, if your goal is just to split your code amongst multiple files, a library might be the right thing. Using subshells (which fork an existing shell instance) is much more efficient than spawning subprocesses (which involve both a fork and an exec).
For instance, you could write prime.bash:
check() {
local number half i rem
number=$1
half=$((number / 2))
for (( i=2; i<half; i++ )); do
rem=$((number % i))
if (( rem == 0 )); then
echo "0"
return
fi
done
echo "1"
}
...and then, in your primefactor script, read in that library and use the function it defined:
source prime.bash # read in the library
clear
echo "Enter number : "
read number
half=$((number / 2))
for(( i=1;i<=half;i++ ))
do
rem=$((number % i))
if (( rem == 0 )); then
ok=$(check "$rem")
if [[ $ok = 1 ]]; then
echo "$i"
fi
fi
done
Call your script like this:
ok=`./prime.sh $rem`

Why test for equality in sh scripts in an indirect way?

I often see this construct in sh scripts:
if [ "z$x" = z ]; then echo x is empty; fi
Why don't they just write it like this?
if [ "$x" = "" ]; then echo x is empty; fi
TL;DR short answer
In this construct:
if [ "z$x" = z ]; then echo x is empty; fi
the z is a guard against funny content of $x and many other problems.
If you write it without the z:
if [ "$x" = "" ]; then echo x is empty; fi
and $x contains the string -x you will get:
if [ "-x" = "" ]; then echo x is empty; fi
and that confuses the hell out of some older implementations of [.
If you further omit the quotes around $x and $x contains the string -f foo -o x you will get:
if [ -f foo -o x = "" ]; then echo x is empty; fi
and now it silently checks for something completely different.
the guard will prevent these maybe honest human errors maybe possibly malicious attacks to fall through silently. with the guard you either get the correct result or an error message. read on for an elaborate explanation.
Elaborate explanation
The z in
if [ "z$x" = z ]; then echo x is empty; fi
is called a guard.
To explain why you want the guard I first want to explain the syntax of the bash conditional if. It is important to understand that [ is not part of the syntax. It is a command. It is an alias to the test command. And in most current shells it is a builtin command.
The grammar rule for if is roughly as follows:
if command; then morecommands; else evenmorecommands; fi
(the else part is optional)
command can be any command. Really any command. What bash does when it encounters an if is roughly as follows:
Execute command.
Check the exit status of command.
If exit status is 0 then execute morecommands. If exit status is anything else, and the else part exists, then execute evenmorecommands.
Let's try that:
$ if true; then echo yay; else echo boo; fi
yay
$ if wat; then echo yay; else echo boo; fi
bash: wat: command not found
boo
$ if echo foo; then echo yay; else echo boo; fi
foo
yay
$ if cat foo; then echo yay; else echo boo; fi
cat: foo: No such file or directory
boo
Let's try the test command:
$ if test z = z; then echo yay; else echo boo; fi
yay
And the alias [:
$ if [ z = z ]; then echo yay; else echo boo; fi
yay
You see [ is not part of the syntax. It is just a command.
Note that the z here has no special meaning. It is just a string.
Let's try the [ command outside of an if:
$ [ z = z ]
Nothing happens? It returned an exit status. You can check the exit status with echo $?.
$ [ z = z ]
$ echo $?
0
Let's try unequal strings:
$ [ z = x ]
$ echo $?
1
Because [ is a command it accepts parameters just like any other commands. In fact, the closing ] is also a parameter, a mandatory parameter which must come last. If it is missing the command will complain:
$ [ z = z
bash: [: missing `]'
It is misleading that bash does the complaining. Actually the builtin command [ does the complaining. We can see more clearly who does the complaining when we invoke the system [:
$ /usr/bin/[ z = z
/usr/bin/[: missing `]'
Interestingly the system [ doesn't always insist on a closing ]:
$ /usr/bin/[ --version
[ (GNU coreutils) 7.4
...
You need a space before the closing ] otherwise it will not be recognized as a parameter:
$ [ z = z]
bash: [: missing `]'
You also need a space after the [ otherwise bash will think you want to execute another command:
$ [z = z]
bash: [z: command not found
This is much more obvious when you use test:
$ testz = z
bash: testz: command not found
Remember [ is just another name for test.
[ can do more than just compare strings. It can compare numbers:
$ [ 1 -eq 1 ]
$ [ 42 -gt 0 ]
It can also check for the existence of files or directories:
$ [ -f filename ]
$ [ -d dirname ]
See help [ or man [ for more information about the capabilities of [ (or test). man will show you the documentation for the system command. help will show you the documentation for the bash builtin command.
Now that I have covered the bases I can answer your question:
Why do people write this:
if [ "z$x" = z ]; then echo x is empty; fi
and not this:
if [ "$x" = "" ]; then echo x is empty; fi
For brevity I will strip off the if because this is only about [.
The z in this construct:
[ "z$x" = z ]
is a guard against funny content of $x in combination with older implementations of [, and/or a guard against human error like forgetting to quote $x.
What happens when $x has funny content like -f?
This
[ "$x" = "" ]
will become
[ "-f" = "" ]
Some older implementations of [ will get confused when the first parameter starts with a -. The z will make sure that the first parameter never starts with a - regardless of content of $x.
[ "z$x" = "z" ]
will become
[ "z-f" = "z" ]
What happens when you forgot to quote $x? Funny content like -f foo -o x can change the entire meaning of the test.
[ $x = "" ]
will become
[ -f foo -o x = "" ]
The test is now checking for the existence of the file foo and then logical or with whether x is the empty string. The worst part is that you won't even notice because there is no error message, only an exit status. If $x comes from user input this can even be used for malicious attacks.
With the guarding z
[ z$x = z ]
will become
[ z-f foo -o x = z ]
At least you will now get an error message:
$ [ z-f foo -o x = z ]; echo $?
bash: [: too many arguments
The guard also helps against the case of undefined variable instead of the empty string. Some older shells had different behaviour for undefined variable and empty string. This problem is basically solved because in modern shells undefined mostly behaves like an empty string.
Summary:
The quote around $x helps to make the undefined cases behave more like the empty string cases.
The guard before $x helps to further prevent all the other problems mentioned above.
The guard before $x will prevent all these possible errors:
Funny content of $x (code injection by malicious user)
old implementations of [ (getting confused if string begins with -)
forgetting to quote $x (will allow -f foo -o x to subvert the meaning of the test)
undefined $x. (older implementations behave differently if undefined)
The guard will either do the right thing or raise an error message.
Modern implementations of [ have fixed some of the problems and modern shells have some solutions for the other cases, but they have pitfalls of their own. The guarding z is not necessary if you are otherwise carefull, but it makes avoiding mistakes while writing simple tests so much more simpler.
See also:
bash pitfalls about quoting in tests
bash FAQ more details about test
more about test
more about quoting
"test" operator robustness in various shells
For testing zero length, use -z:
if [ -z "$x" ] ; then
echo x is empty
fi
With bash, you can use its [[ that does not need quotes:
if [[ -z $x ]] ; then
echo x is empty
fi
I just found the following in man 1p sh, the documentation of POSIX shell:
Historical systems have also been unreliable given the common construct:
test "$response" = "expected string"
One of the following is a more reliable form:
test "X$response" = "Xexpected string"
test "expected string" = "$response"
Note that the second form assumes that expected string could not be confused with any unary primary. If expected string starts with '-', '(', '!', or even '=', the first form should be used instead.
The short and simple answer: [ is actually not a bash directive, while [[ is. It is instead a symlink to the command line utility test.
Now for why:
Like any other command line utility, test interprets anything starting with a - to be an option. It also considers anything starting with = to be an operator. If you don't prefix your arguments to [ (or test) with an alpha character, there is no guarantee the test will work reliably.
Consider the values:
a=1
b=1
And the evaluation:
[ "$a" = "$b" ] && echo "Yes, they match"
Which is essentially running the following command (test ignores the closing ] when its exec name is [):
test 1 = 1 ] && echo "Yes, they match"
Now consider the values:
a="-lt"
b="-lt"
The argument -lt is an option to test. So when you perform the same test, it expands to:
test -lt = -lt ] && echo "Yes, they match"
Now, this is fine on Linux systems (or at least modern ones), since test has been rewritten to ignore options that precede or succeed the = or != operators. However, on some older UNIX systems, this will break with an error like:
test: -lt: unary operator expected
if you want to make sure x is defined :
if [ ${x:-Z} = 'Z' ];then
echo x is empty
fi

Meaning of "[: too many arguments" error from if [] (square brackets)

I couldn't find any one simple straightforward resource spelling out the meaning of and fix for the following BASH shell error, so I'm posting what I found after researching it.
The error:
-bash: [: too many arguments
Google-friendly version: bash open square bracket colon too many arguments.
Context: an if condition in single square brackets with a simple comparison operator like equals, greater than etc, for example:
VARIABLE=$(/some/command);
if [ $VARIABLE == 0 ]; then
# some action
fi
If your $VARIABLE is a string containing spaces or other special characters, and single square brackets are used (which is a shortcut for the test command), then the string may be split out into multiple words. Each of these is treated as a separate argument.
So that one variable is split out into many arguments:
VARIABLE=$(/some/command);
# returns "hello world"
if [ $VARIABLE == 0 ]; then
# fails as if you wrote:
# if [ hello world == 0 ]
fi
The same will be true for any function call that puts down a string containing spaces or other special characters.
Easy fix
Wrap the variable output in double quotes, forcing it to stay as one string (therefore one argument). For example,
VARIABLE=$(/some/command);
if [ "$VARIABLE" == 0 ]; then
# some action
fi
Simple as that. But skip to "Also beware..." below if you also can't guarantee your variable won't be an empty string, or a string that contains nothing but whitespace.
Or, an alternate fix is to use double square brackets (which is a shortcut for the new test command).
This exists only in bash (and apparently korn and zsh) however, and so may not be compatible with default shells called by /bin/sh etc.
This means on some systems, it might work from the console but not when called elsewhere, like from cron, depending on how everything is configured.
It would look like this:
VARIABLE=$(/some/command);
if [[ $VARIABLE == 0 ]]; then
# some action
fi
If your command contains double square brackets like this and you get errors in logs but it works from the console, try swapping out the [[ for an alternative suggested here, or, ensure that whatever runs your script uses a shell that supports [[ aka new test.
Also beware of the [: unary operator expected error
If you're seeing the "too many arguments" error, chances are you're getting a string from a function with unpredictable output. If it's also possible to get an empty string (or all whitespace string), this would be treated as zero arguments even with the above "quick fix", and would fail with [: unary operator expected
It's the same 'gotcha' if you're used to other languages - you don't expect the contents of a variable to be effectively printed into the code like this before it is evaluated.
Here's an example that prevents both the [: too many arguments and the [: unary operator expected errors: replacing the output with a default value if it is empty (in this example, 0), with double quotes wrapped around the whole thing:
VARIABLE=$(/some/command);
if [ "${VARIABLE:-0}" == 0 ]; then
# some action
fi
(here, the action will happen if $VARIABLE is 0, or empty. Naturally, you should change the 0 (the default value) to a different default value if different behaviour is wanted)
Final note: Since [ is a shortcut for test, all the above is also true for the error test: too many arguments (and also test: unary operator expected)
Just bumped into this post, by getting the same error, trying to test if two variables are both empty (or non-empty). That turns out to be a compound comparison - 7.3. Other Comparison Operators - Advanced Bash-Scripting Guide; and I thought I should note the following:
I used -e thinking it means "empty" at first; but that means "file exists" - use -z for testing empty variable (string)
String variables need to be quoted
For compound logical AND comparison, either:
use two tests and && them: [ ... ] && [ ... ]
or use the -a operator in a single test: [ ... -a ... ]
Here is a working command (searching through all txt files in a directory, and dumping those that grep finds contain both of two words):
find /usr/share/doc -name '*.txt' | while read file; do \
a1=$(grep -H "description" $file); \
a2=$(grep -H "changes" $file); \
[ ! -z "$a1" -a ! -z "$a2" ] && echo -e "$a1 \n $a2" ; \
done
Edit 12 Aug 2013: related problem note:
Note that when checking string equality with classic test (single square bracket [), you MUST have a space between the "is equal" operator, which in this case is a single "equals" = sign (although two equals' signs == seem to be accepted as equality operator too). Thus, this fails (silently):
$ if [ "1"=="" ] ; then echo A; else echo B; fi
A
$ if [ "1"="" ] ; then echo A; else echo B; fi
A
$ if [ "1"="" ] && [ "1"="1" ] ; then echo A; else echo B; fi
A
$ if [ "1"=="" ] && [ "1"=="1" ] ; then echo A; else echo B; fi
A
... but add the space - and all looks good:
$ if [ "1" = "" ] ; then echo A; else echo B; fi
B
$ if [ "1" == "" ] ; then echo A; else echo B; fi
B
$ if [ "1" = "" -a "1" = "1" ] ; then echo A; else echo B; fi
B
$ if [ "1" == "" -a "1" == "1" ] ; then echo A; else echo B; fi
B
Another scenario that you can get the [: too many arguments or [: a: binary operator expected errors is if you try to test for all arguments "$#"
if [ -z "$#" ]
then
echo "Argument required."
fi
It works correctly if you call foo.sh or foo.sh arg1. But if you pass multiple args like foo.sh arg1 arg2, you will get errors. This is because it's being expanded to [ -z arg1 arg2 ], which is not a valid syntax.
The correct way to check for existence of arguments is [ "$#" -eq 0 ]. ($# is the number of arguments).
I also faced same problem. #sdaau answer helped me in logical way. Here what I was doing which seems syntactically correct to me but getting too many arguments error.
Wrong Syntax:
if [ $Name != '' ] && [ $age != '' ] && [ $sex != '' ] && [ $birthyear != '' ] && [ $gender != '' ]
then
echo "$Name"
echo "$age"
echo "$sex"
echo "$birthyear"
echo "$gender"
else
echo "Enter all the values"
fi
in above if statement, if I pass the values of variable as mentioned below then also I was getting syntax error
export "Name"="John"
export "age"="31"
export "birthyear"="1990"
export "gender"="M"
With below syntax I am getting expected output.
Correct syntax:
if [ "$Name" != "" -a "$age" != "" -a "$sex" != "" -a "$birthyear" != "" -a "$gender" != "" ]
then
echo "$Name"
echo "$age"
echo "$sex"
echo "$birthyear"
echo "$gender"
else
echo "it failed"
fi
There are few points which we need to keep in mind
use "" instead of ''
use -a instead of &&
put space before and after operator sign like [ a = b], don't use as [ a=b ] in if condition
Hence above solution worked for me !!!
Some times If you touch the keyboard accidentally and removed a space.
if [ "$myvar" = "something"]; then
do something
fi
Will trigger this error message. Note the space before ']' is required.
I have had same problem with my scripts. But when I did some modifications it worked for me. I did like this :-
export k=$(date "+%k");
if [ $k -ge 16 ]
then exit 0;
else
echo "good job for nothing";
fi;
that way I resolved my problem. Hope that will help for you too.

Resources