I do not understand this line of shell script. Doesn\'t the while statement need a \'test\' or [ ] or [[ ]] expression that will set $? to 1 or 0? How does
w
I do not understand this line of shell script. Doesn't the while statement need a 'test' or [ ] or [[ ]] expression that will set $? to 1 or 0? How does [the statement] do that?
Everyone is answering about the -d
and IFS=
parameter, but no one answered your question about test
and [[..]]
.
All standard Unix commands will return a zero value on success and a non-zero value on exit. The while
and if
statements use that value to determine whether or not to continue to loop:
Let's say you do the following:
$ touch foo.txt #Create a "foo.txt" file
$ ls foo.txt
foo.txt
$ echo $?
0
$
Notice that the ls
command returned a zero on exit. Now try this:
$ rm foo.txt
$ ls foo.txt
ls: foo.txt: No such file or directory
$ echo $?
1
$
If foo.txt
doesn't exist, the ls
command returns a 1 on exit.
The if
and while
commands operate on that.
Go into another terminal window and create a foo.txt
file. Then return to the original window and try this:
$ while ls foo.txt > /dev/null
> do
> echo "The file foo.txt exists"
> sleep 2
> done
The file foo.txt exists
The file foo.txt exists
The file foo.txt exists
...
Every two seconds, the while will loop through checking for the existence of foo.txt
and print out that statement. Now, go back to the second terminal window and delete foo.txt
. If you go back to the first terminal window, you'll see that the while
loop ended.
You can do the same with the if
:
$ if ls foo.txt > /dev/null
> then
> echo "foo.txt exists"
> else
> echo "Whoops, it's gone!"
> fi
This if
statement will print either foo.txt exists or Whoops, it's gone! depending whether or not a file called foo.txt
exists in the directory.
Notice that if
and while
work upon the return exit value of the command. There's no need for a test at all.
So why the test
, [...]
, and [[...]]
syntax?
Let's say you want to find out whether $foo
is equal to $bar
. That's where test
comes into play. Try this:
$ foo=42
$ bar=42
$ test "$foo" -eq "$bar"
$ echo $?
0
$ bar=0
$ test "$foo" -eq "$bar"
$ echo $?
1
All the test command does is return a zero on true tests and a non-zero value on false tests. This allows you to use the test
command in if
and while
statements.
$ if test $foo -eq $bar
> then
> echo "foo is equal to bar"
> else
> echo "foo and bar are different"
> fi
foo and bar are different
$
So what's [...]
?
$ ls -il /bin/test /bin/[
10958 -rwxr-xr-x 2 root wheel 18576 May 28 22:27 /bin/[
10958 -rwxr-xr-x 2 root wheel 18576 May 28 22:27 /bin/test
That first column is the inode. If two files have the same inode, they are hard linked and refer to the same file. Notice that they're both 18,576 bytes on my system.
The [
is just another way to do the test
command itself. Both of these lines are the same:
if test $foo -eq $bar
if [ $foo -eq $bar ]
The [
was created to make shell scripts look a wee bit nicer. However, underneath, it's running a test
command which will return either a zero or non-zero value that if
and while
can use.
By the way, in Kornshell and BASH, both [
and test
are builtin to the shell. But, they do reference the same builtin command.
The [[...]]
is a special version of the test that first appeared in Kornshell. It allows for the use of pattern matches and it's a bit faster since it doesn't spawn another process to run the test
command.
Code explanations :
IFS=
set IFS to null (input fiels separator)read
command that reads the current line with the following options :-r
: do not allow backslashes to escape any characters-d
continue until the first character of DELIM is read, rather than newline$'\0'
zero byte (the ASCII NUL character) See
help read
In Bash, varname=value command
runs command with the environment variable varname
set to value
(and all other environment variables inherited normally). So IFS= read -r -d $'\0'
runs the command read -r -d $'\0'
with the environment variable IFS
set to the empty string (meaning no field separators).
Since read
returns success (i.e., sets $?
to 0
) whenever it successfully reads input and doesn't encounter end-of-file, the overall effect is to loop over a set of NUL-separated records (saved in the variable REPLY
).
Doesn't the while statement need a 'test' or [ ] or [[ ]] expression that will set $? to 1 or 0?
test
and [ ... ]
and [[ ... ]]
aren't actually expressions, but commands. In Bash, every command returns either success (setting $?
to 0
) or failure (setting $?
to a non-zero value, often 1
).
(By the way, as nosid notes in a comment above, -d $'\0'
is equivalent to -d ''
. Bash variables are internally represented as C-style/NUL-terminated strings, so you can't really include a NUL in a string; e.g., echo $'a\0b'
just prints a
.)
No. The test
command and [[ ...]]
expression are merely constructs that supply an exit status (the only thing while
cares about) that reflects the truth of the conditional expression it contains.
For example, the exit status of [[ $foo = bar ]]
is 0 (success) if $foo
has the value bar
, and a non-zero exist status (specifically 1) otherwise.
A while
loop executes its body as long as the exit status of the command following the while
keyword is 0. In this code, read
has a 0 exit status as long as it can read something from its input.