I found some strange thing in bash and I can't understand how it works.
[test ~]$ a=""
[test ~]$ $a && echo 1
1
[test ~]$ $a
[test ~]$ echo $?
0
Why does $a (which is empty) return 0? Is it somehow transformed to empty command?
If I add quotes or write empty string before &&, it will return error. While empty command returns 0.
[test ~]$ "$a" && echo 1
-bash: : command not found
[test ~]$ "" && echo 1
-bash: : command not found
[test ~]$ `` && echo 1
1
So, what is happening when I type $a?
You seem to confuse bash with some other programming language. Variables get replaced, then what is left gets executed.
"$a"
This is the content of a, between quotation marks. a is empty, so this is equivalent to:
""
That is not a command. "Command not found." As there was an error, the execution was not successful (shell return code is not 0), so the second half of the command -- && echo 1 -- does not get executed.
Backticks...
``
...execute whatever is between them, with the output of that command replacing the whole construct. (There is also $() which does the same, and is less prone to being overlooked in a script.) So...
`echo "foo"`
...would evaluate to...
foo
...which would then be executed. So your...
``
...evaluates to...
<empty>
...which is then "executed successfully" (since there is no error).
If you want to test the contents of a, and execute echo 1 only if a is not empty, you should use the test command:
test -n "$a" && echo 1
There is a convenient alias for test, which is [, which also conveniently ignores a trailing ]...
[ -n "$a" ] && echo 1
...and a bash-ism [[ that "knows" about variable replacement and thus does not need quotation marks to avoid complaining about a missing argument if $a does indeed evaluate to empty...
[[ -n $a ]] && echo 1
...or, of course, the more verbose...
if [[ -n $a ]]
then
echo 1
fi
Ah. Missed the core part of the question:
$a && echo 1
This is two statements, separated by &&. The second statement only gets executed if the first one executes OK. The bash takes the line apart and executes the first statement:
$a
This is...
<empty>
...which is "successful", so the second statement gets executed. Opposed to that...
&& echo 1
...is a syntax error because there is no first statement. ;-) (Tricky, I know, but that's the way this cookie crumbles.)
a=""
or
a=" " #a long empty string
then
$> $a
will return 0
$> $noExistVar
will also return 0.
They get "executed", in fact, nothing gets executed. same as you press enter or pressing 10 spaces then enter, you get return code 0 too.
$> && echo 1
this will fail, because bash will try to execute the first part, in this case it is missing.
$> $notExistVar && echo 1
Here it works, I guess bash found the first part the $whatever, therefore no syntax error. Then "execute" it, well nothing to execute, return 0, (same as pressing enter after prompt), then check, if first part returned 0, exec the cmd after &&.
I said guess because I didn't check bash's source codes. Please correct me if it is wrong.
the $> " " && echo 1 case, I think it is clear, don't need to explain.
Related
I read this question, but my problem is that I have "plenty" of commands to run; and I need a solution that works for a systems calls.
We have an exit task that basically triggers a lot of "cleanup" activity within our JVM. The part I am working on has to call a certain script, not once, but n times!
The current implementation on the Java side creates n ProcessBuilder objects; and each one runs a simple bash script.sh parm ... where parm is different on each run.
Now I want to change the Java side to only make one system call (instead of n) using ProcessBuilder.
I could just use the following command:
bash script.sh parm1 ; bash script.sh parm2 ; ... ; bash script.sh parmN
Now the thing is: if one of the runs fails ... I want all other runs to still take place; but I would like to get a "bad" return code in the end.
Is there a simple, elegant way to achieve that, one that works with command strings coming from system calls?
You can build up the return codes in a subshell as you go, then check them at the end using arithmetic evaluation. E.g., on my test system (cygwin), at a bash prompt:
$ ( r=; echo "foo" ; r=$r$?; echo "bar" ; r=$r$? ; echo "baz" ; r=$r$? ; (($r==0)) )
foo
bar
baz
$ echo $?
0 <--- all the commands in the subshell worked OK, so the status is OK
and
VVVV make this echo fail
$ ( r=; echo "foo" ; r=$r$?; echo "bar" 1>&- ; r=$r$? ; echo "baz" ; r=$r$? ; (($r==0)) )
foo
-bash: echo: write error: Bad file descriptor
baz
$ echo $?
1 <--- failure code, but all the commands in the subshell still ran.
So, in your case,
(r=; bash script.sh parm1 ; r=$r$?; bash script.sh parm2 ; r=$r$?; ... ; bash script.sh parmN r=$r$?; (($r==0)) )
You can also make that slightly shorter with a function s that stashes the return code:
$ (r=;s(){ r=$r$?;}; echo "foo" ; s; echo "bar" 1>&-; s; echo "baz" ; s; (($r==0)) )
foo
-bash: echo: write error: Bad file descriptor
baz
$ echo $?
1
s(){ r=$r$?;} defines a function s that will update r. Then s can be run after each command. The space and semicolon in the definition of s are required.
What's happening?
r= initializes r to an empty string. That will hold our return values as we go.
After each command, r=$r$? tacks that command's exit status onto r. There are no spaces in r, by construction, so I left off the quotes for brevity. See below for a note about negative return values.
At the end, (($r==0)) succeeds if r evaluates to 0. So, if all commands succeeded, r will be 000...0, which equals 0.
The exit status of a subshell is the exit status of its last command, here, the (($r==0)) test. So if r is all zeros, the subshell will report success. If not, the subshell will report failure ($?==1).
Negative exit values
If some of the programs in the subshell may have negative exit values, this will probably still work. For example, 0-255100255 is a valid expression that is not equal to zero. However, if you had two commands, the first exited with 127, and the second exited with -127, r would be 127-127, which is zero.
To avoid this problem, replace each r=$r$? with r=$r$((! ! $?)). The double logical negation $((! ! $?)) converts 0 to 0 and any other value, positive or negative, to 1. Then r will only contain 0 and 1 values, so the (($r==0)) test will be correct. (You do need spaces after each ! so bash doesn't think you're trying to refer to your command history.)
A binary OR-ing of all exit codes will be zero (0) "if" and "only if" all exit codes are zero (0).
You could get a running exit code with this simple arithmetic expression:
excode=((excode | $?))
To run all parameters, you could use an script ("callcmd") like:
#!/bin/bash
excode=0
for i
do cmd "$i"
excode=((excode | $?))
done
echo "The final exit code is $excode"
# if [[ $excode -ne 0 ]]; exit 1; fi # An alternative exit solution.
Where this script ("callcmd") is called from java as:
callcmd parm1 parm2 parm3 … parmi … parmN
The output of each command is available at the usual standard output and the error strings (if any) will also be available in the stderr (but all will be joined, so the command "cmd" should identify for which parm is emitting the error).
r=0; for parm in parm1 parm2 ... parmN; do
bash script.sh "$parm" || r=1
done
exit "$r"
You can do it like this
bash script.sh parm1 || echo "FAILED" > some.log ; bash script.sh parm2 || echo "FAILED" > some.log; ... ; bash script.sh parmN|| echo "FAILED" > some.log
Then check if there is some.log file.
|| - It's simple bash logical or ( executed if exit status of previous one is non-zero)
Below is the code of bash:
a=`echo hello`
echo $a
output is :
hello
But I think it should be:
hello
0
You think wrong ;-)
Putting the command in backticks assigns the output (stdout) from the expression on the right to the variable on the left.
$? gives you the "output status" (or return code) of the command - aka the "0" you were expecting.
So:
a=`echo hello`
Runs the command "echo hello" but instead of echoing to stdout, it "echoes" to varaiable a. So a=whatever_the_command_would_have_written_to_stdout (in this case "hello") - nothing is actually written to stdout because it is "captured" by the ``s
You mistakenly think that a=`echo hello`:
executes echo hello and prints its stdout output directly to the caller's stdout,
and then assigns the exit code (return value) of the echo command to variable $a.
Neither is true; instead:
echo hello's stdout output is captured in memory (without printing to the caller's stdout; that's how command substitutions work),
and that captured output is assigned to $a.
A command's exit code (a return value indicating success vs. failure) is never directly returned in POSIX-like shells such as Bash.
The only way to use an exit code is either:
explicitly, by accessing special variable $? immediately after the command ($? contains the most recent command's exit code)
implicitly, in conditionals (a command whose exit code is 0 evaluates to true in a conditional, any other exit code implies false).
Thus, to achieve what you're really trying to do, use:
echo 'hello' # Execute a command directly (its stdout output goes to the caller's stdout)
a=$? # Save the previous command's exit code in var. $a
echo "$a" # Echo the saved exit code.
As this [ this ] answer already mentioned, the return value for the last executed command is stored in
$? # ${?} is sometimes needed
If you wish a to contain 'hello' and the return value of echo hello in separate lines, ie
hello
0
below is one way to do it
$ a=`echo -en "hello\n" && echo -n $?` # here $? is ret val for 1st echo
$ echo -e "$a"
hello
0
Note
-n with echo suppresses the trailing new line
-e with echo interprets escape sequences
Since && is the logical and operator, the second echo wouldn't have been executed had the first echo failed
Another important point to note is that even the assignment ie
a=b
has a return value.
What environment variable or something internally the 'if' keyword checks to decide true/false.
I am having something like below two statements. abc is mounted , but not pqr.
if mount |grep -q "abc"; then echo "export pqr"; fi
if mount |grep -q "pqr"; then echo "export abc"; fi
In the above case I expected first statement to do nothing since abc is mounted(so finds the row in mount o/p) hence the $? after mount |grep -q "abc" is 0.
And I expected second statement to execute the echo. But it's happening otherwise, first statement is printing but second not. So want to understand on what basis if decides true/false.
Here is one related question
But the accepted answer for that question says :
if [ 0 ]
is equivalent to
if [ 1 ]
If this is true then both my statements should do echo, right?
There is a basic difference between the commands that you are issuing and the analogy that you are drawing from the referenced question.
When grep is executed with the -q option, it exits with a return code of zero is the match is found. This implies that if the output of mount were to contain abc, then
if mount |grep -q "abc"; then echo "export pqr"; fi
is equivalent to saying:
if true; then echo "export pqr"; fi
Note that there is no test command, i.e. [, that comes into the picture here.
Quoting from the manual:
The test and [ builtins evaluate conditional expressions using a set
of rules based on the number of arguments.
0 arguments
The expression is false.
1 argument
The expression is true if and only if the argument is not null.
This explains why [ 0 ] and [ 1 ] both evaluate to true.
The if command does not act, like C-like languages, on the "boolean value" of an integer: it acts on the exit status of the command that follows. In shell, an exit status of 0 is considered to be success, any other exit status is failure. If the command following if exits with status 0, that is "true"
Example:
$ test -f /etc/passwd; echo $?
0
$ test -f /etc/doesnotexist; echo $?
1
$ if test -f /etc/passwd; then echo exists; else echo does not exist; fi
exists
$ if test -f /etc/doesnotexist; then echo exists; else echo does not exist; fi
does not exist
Note that [ and [[ are (basically) commands that (basically) alias test
if [ 0 ]
tests to see if the string 0 is non-empty. It is non-empty, so the test succeeds. Similarly, if [ 1 ] succeeds because the string 1 is non-empty. The [ command (also named test) is returning a value based on its arguments. Similarly, grep returns a value.
The if keyword causes the shell to execute commands based on the value returned by the command, but the output of the command preceded by if is irrelevant.
The command test 0 (equivalent to the command [ 0 ] returns a value of 0. The command test 1 also returns a value of 0. Zero is treated by the shell as a success, so the commands of the if clause are executed.
I've reviewing some bash scripts written by other people at work and I found this line that I'm trying to understand
[[ $(awk 'BEGIN{print ('$CAPACITY'>=0.9)}') -eq 1 ]] && echo "Capacity at 90 Percent"
Is my understanding that this line is replacing an if statement. Could someone help me out explaining what this line really does. Thanks
This makes me very sad and pessimistic about the future of civilization...
Let's break this down:
[[ $(awk 'BEGIN{print ('$CAPACITY'>=0.9)}') -eq 1 ]] && echo "Capacity at 90 Percent"
Note the $(....). This tells the shell to execute the program inside, and replace the contents of $(...) with the value. For example:
$ file_name="/usr/local/bin/foo"
$ short_name="$(basename $file_name)"
$ echo $short_name
foo
In this the second line, we are running the command basename $file_name. This returns foo. Then, the shell will substitute foo for $(basename $filename) before assigning short_name. Here's the same thing with the debugger on:
$ set -xv
$ file_name="/usr/local/bin/foo"
foo=/usr/local/bin/foo
+ foo=/usr/local/bin/foo
$ short_name=$(basename $file_name)
short_name=$(basename $file_name)
basename $file_name
++ basename /usr/local/bin/foo
+ short_name=foo
$ echo $short_name
echo $short_name
+ echo foo
foo
$ set +xv # Turn off the debugger.
You can see how the shell executes $(...) and replaces it.
Thus, the user is actually running the program:
awk 'BEGIN{print ('$CAPACITY'>=0.9)}'
However, take a look at the quotation marks:
awk 'BEGIN{print ('$CAPACITY'>=0.9)}'
+++++++++++++ +++++++
The stuff with the pluses under it are part of the awk command inside single quotes and thus cannot be interpolated by the shell. HOWEVER, $CAPACITY is not in quotes. In other words, the value of $CAPACITY replaces that variable before the awk command is executed. Thus, if $CAPACITY is .8, the awk command will become:
awk 'BEGIN{print ('.8'>=0.9)}`
That's the very first part of the explanation.
Now on to the next part. How much do you know about awk?
Awk is a programming language that's usually part of Unix/Linux distributions. Awk normally works on files and assumes a loop around the file with each line being read in and operated on. For example:
$ awk '{print $1}` foo.txt
Let's assume that each line in foo.txt consists of several fields that are separated by spaces. The file foo.txt is read in and each line is passed through to the awk program and the awk program will print out the first field of each line.
However, there is no file for awk to operate on. This developer is using the special patter BEGIN. This is executed before any lines are read in. Since there is no file for awk to process, and there is no actual awk program (only a BEGIN statement), awk will execute this statement (assuming capacity is at 80%:
.8 >= .9
Like in Shell and other programming languages. This statement will evaluate as true or false. In awk, if this statement is true, it will a non-zero value (we hope 1). If it is false, it will equal zero. In this case, it will equal false.
Awk returns (like Perl) the last value it executes. Thus, if the capacity is at 80%, the awk statement .8 >= .9 will be false. Awk will return a zero.
Now, the entire $([[ $(awk 'BEGIN{print ('$CAPACITY'>=0.9)}') will be replaced with 0. Your [[ ... ]] test now becomes:
[[ 0 -eq 1 ]] && echo "Capacity at 90 Percent"
Well, [[ 0 -eq 1 ]] is false.
Now the final part.
The two commands && and || are list operators. Their name comes from the C programming operators of the same name, and the way C short circuits tests. For example,
if ( ( bar > 20 ) && ( foo < 30 ) ) {
is a typical C if statement. with foo and bar being variables. I am asking if bar is greater than 20 AND if foo is less than 30 to do something.
C will first evaluate bar > 20 and decide whether it is true or false. If bar > 20 is false, there's is no need to test foo < 30 because no matter what the results are, the statement is still false. What if bar is indeed greater than 20? You have to run the next part of the if statement.
Imagine this:
if ( ( bar > 20 ) || ( foo < 30 ) ) {
This says if bar is greater than 20 OR foo < 30. In this case, C will evaluate whether bar is greater than 20. If it is, there is no need to test whether or not foo is less than 30. The statement will be true no matter what the value of foo is. What if bar isn't greater than 20? Then, I have to test the value of foo.
So, if I have && and the first statement is false, don't do the second statement (the entire expression is false anyway). If the first statement is true, I have to run the second statement (because I don't know whether or not that entire statement is true or not).
If I have ||, the complete opposite happens. If the first statement is true, don't do the second statement (because the entire expression is true). If that first statement is false, I have to run the second statement.
The gist of this is:
[ "$foo" = "$bar" ] && echo "Foo equals bar"
is the same as:
if [ "$foo" = "$bar" ]
then
echo "Foo equals bar"
fi
Because if $foo does equal $bar, I have to execute the second part of the statement!
And, this:
[ "$foo" = "$bar" ] || echo "Foo and Bar are not equal"
is the same as:
if [ "$foo" != "$bar" ]
then
echo "Foo and Bar are not equal"
fi
So, first the shell substitutes in the value of the shell variable $CAPACITY into your little awk script.
Next the awk script runs testing whether or not the substituted value of $CAPACITY is greater than or equal to 0.9. Since there is no actual awk program, awk doesn't attempt to read in from STDIN.
Next, awk will assign a zero or non-zero value to that boolean statement (depending whether or not it's true). Then, the awk program will exit with the evaluated value of that boolean statement.
The shell now substitutes that zero or non-zero value for that entire $(...) phrase. This is run through a test to see if it is or isn't equal to 1.
Finally if that test statement is equal to 1, the && will tell the shell to evaluate that echo statement. Thus, if the shell variable $CAPACITY is .9 or greater, that echo statement will print.
That's a lot of machinations just to compare .8 (or whatever the capacity is) with .9, so why did the developer do this?
Probably because BASH shell can only do integer arithmetic. Since $CAPACITY is less than one, you can't do this:
if [[ $CAPACITY -le .9 ]]
then
echo "Capacity is at 90%"
fi
Instead of using awk, I would probably have used bc:
OVER_CAPACITY=$(bc <<<"$CAPACITY >= .9")
if [[ ! $OVER_CAPACITY -eq $(true) ]]
then
echo "Capacity is over 90%"
else
echo "Every thing is okay"
fi
It would have been a few more lines, but I hope it makes things a bit easier to understand and make the file easier to maintain.
The complete line can be thought of as [[ if something is true ]] &&=then do another thing
To understand what is going on in this code, turn on your mental shell parser, and find the innermost construct that will produce output. in this case
awk 'BEGIN{print ('$CAPACITY'>=0.9)}'
execute that on a cmd-line by itself. Obviously the variable CAPACITY has to be set with a value.
Then you can use the shell debug/trace facility (set -vx) to see every thing executing
CAPACITY=0.95
set -vx
[[ $(awk 'BEGIN{print ('$CAPACITY'>=0.9)}') -eq 1 ]] && echo "Capacity at 90 Percent"
+ awk 'BEGIN{print (0.95>=0.9)}'
+ [[ 1 -eq 1 ]]
+ echo 'Capacity at 90 Percent'
Capacity at 90 Percent
IHTH
It's not, the [[ and ]] are an improvement upon the test builtin and the && is an AND
So, what this line is doing equivalent to:
if [[ $(awk 'BEGIN{print ('$CAPACITY'>=0.9)}') -eq 1 ]] ; then
echo "Capacity at 90 Percent"
fi
In effect, the line is saying TEST this condition AND do this other thing only if it's TRUE
Similarly, you could do [[ something_to_test ]] || do this if something_to_test is false
which means, TEST this condition OR do this other thing
These are bash shell one-line shortcuts.
You got a lot of good explanations, now rewrite the whole thing as:
awk -v cap="$CAPACITY" 'BEGIN{ if (cap>=0.9) print "Capacity at 90 Percent" }'
for clarity and simplicity.
[[ .... ]] construct returns true or false.
so the exp in [[ .... ]] must be a logic operation
code:
path=$PATH:
while [ -n $path ]
do
ls -ld ${path%%:*}
path=${path#*:}
done
I want to get the each part of path .When run the script ,it can not get out of the while process 。Please tell me why . Is some problem in 'while [ -n $path ]' ?
The final cut never results in an empty string. If you have a:b:c, you'll strip off the a and then the b, but never the c. I.e., this:
${path#*:}
Will always result in a non-empty string for the last piece of the path. Since the -n check looks for an empty string, your loop runs forever.
If $path doesn't have a colon in it, ${path#*:} will return $path. So you have an infinite loop.
p="foo"
$ echo ${p#*:}
foo
$ p="foo:bar"
$ echo ${p#*:}
bar
You have some bugs in your code. This should do the trick:
path=$PATH
while [[ $path != '' ]]; do
# you can replace echo to whatever you need, like ls -ld
echo ${path%%:*}
if echo $path | grep ':' >/dev/null; then
path=${path#*:}
else path=''
fi
done
Your path, after is initialized, will always check True for [ -n path ] test. This is the main reason for which you never get out of the while loop.