Flow Control
Exit Status
Every UNIX command, whether it comes from source code in C, some other language, or a shell script/function, returns an integer code to its calling process—the shell in this case—when it finishes. This is called the exit status. 0 is usually the OK exit status, while anything else (1 to 255) usually denotes an error.
pushd ( ) { dirname=$1 DIR_STACK="$dirname ${DIR_STACK:-$PWD' '}" cd ${dirname:?"missing directory name."} echo $DIR_STACK }
This function requires a valid directory as its argument. Let’s look at how it handles error conditions: if no argument is given, the third line of code prints an error message and exits. This is fine.
However, the function reacts deceptively when an argument is given that isn’t a valid directory. In case you didn’t figure it out when reading the last chapter, here is what happens: the cd fails, leaving you in the same directory you were in. This is also appropriate. But the second line of code has pushed the bad directory onto the stack anyway, and the last line prints a message that leads you to believe that the push was successful. Even placing the cd before the stack assignment won’t help because it doesn’t exit the function if there is an error.
We need to prevent the bad directory from being pushed and to print an error message. Here is how we can do this:
pushd ( ) { dirname=$1 if cd ${dirname:?"missing directory name."} # if cd was successful then DIR_STACK="$dirname ${DIR_STACK:-$PWD' '}" # push the directory echo $DIR_STACK else echo still in $PWD. # else do nothing fi }
Let’s say you have the following code in your .bash_profile.
cd ( ) { builtin cd "$@" echo "$OLDPWD —> $PWD" }
The builtin command allows us to do this. builtin tells the shell to use the built-in command and ignore any function of that name. Using builtin is easy; you just give it the name of the built-in you want to execute and any parameters you want to pass. If you pass in the name of something which isn’t a built-in command, builtin will display an appropriate message. For example: builtin: alice: not a shell builtin.
We want this function to return the same exit status that the built-in cd returns. The problem is that the exit status is reset by every command, so it “disappears” if you don’t save it immediately. In this function, the built-in cd’s exit status disappears when the echo statement runs (and sets its own exit status).
Therefore, we need to save the status that cd sets and use it as the entire function’s exit status. Two shell features we haven’t seen yet provide the way. First is the special shell variable ?, whose value ($?) is the exit status of the last command that ran.
For example:
So, to save the exit status we need to assign the value of ? to a variable with the line es=$? right after the cd is done.
Return
return can only be used inside functions, and shell scripts that have been executed with source. In contrast, the statement exit N exits the entire script, no matter how deeply you are
nested in functions.
cd ( ) { builtin cd "$@" es=$? echo "$OLDPWD —> $PWD" return $es }
Combinations of Exit Statuses
statement1 && statement2
The syntax statement1 && statement2 means, “execute statement1, and if its exit status is 0, execute statement2.”
tatement1 || statement2
The syntax statement1 || statement2 is the converse: it means, “execute statement1, and if its exit status is not 0, execute statement2.”
Condition Tests
[ condition ]
String comparisons
Operator |
True if... |
str1 = str2 |
str1 matches str2 |
str1 != str2 |
str1 does not match str2 |
str1 < str2 |
str1 is less than str2 |
str1 > str2 |
str1 is greater than str2 |
-n str1 |
str1 is not null (has length greater than 0) |
-z str1 |
str1 is null (has length 0) |
File attribute checking
Operator |
True if... |
-a file |
file exists |
-d file |
file exists and is a directory |
-e file |
file exists; same as -a |
-f file |
file exists and is a regular file (i.e., not a directory or other special type of file) |
-r file |
You have read permission on file |
-s file |
file exists and is not empty |
-w file |
You have write permission on file |
-x file |
You have execute permission on file, or directory search permission if it is a directory |
-N file |
file was modified since it was last read |
-O file |
You own file |
-G file |
file’s group ID matches yours (or one of yours, if you are in multiple groups) |
file1 -nt file2 |
file1 is newer than file2 |
file1 -ot file2 |
file1 is older than file2 |
You can also negate the truth value of a conditional expression by preceding it with an exclamation point (!), so that ! expr evaluates to true only if expr is false. Furthermore, you can make complex logical expressions of conditional operators by grouping them with parentheses (which must be “escaped” with backslashes to prevent the shell from treating them specially), and by using two logical operators we haven’t seen yet: -a (AND) and -o (OR).
The -a and -o operators are similar to the && and || operators used with exit statuses. However, unlike those operators, -a and -o are only available inside a test conditional expression.
Here is how we would use two of the file operators, a logical operator, and a string operator to fix the problem of duplicate stack entries in our pushd function. Instead of having cd determine whether the argument given is a valid directory—i.e., by returning with a bad exit status if it’s not—we can do the checking ourselves. Here is the code:
pushd ( ) { dirname=$1 if [ -n "$dirname" ] && [ ( -d "$dirname" ) -a ( -x "$dirname" ) ]; then DIR_STACK="$dirname ${DIR_STACK:-$PWD' '}" cd $dirname echo "$DIR_STACK" else echo "still in $PWD." fi }
The conditional expression evaluates to true only if the argument $1 is not null (-n), a directory (-d) and the user has permission to change to it (-x). * Notice that this conditional handles the case where the argument is missing ($dirname is null) first; if it is,the rest of the condition is not executed. This is important because, if we had just put:
if [ ( -n "$dirname") -a ( -d "$dirname" ) -a ( -x "$dirname" ) ]; then
Task 5-1Write a script that prints essentially the same information as ls -l but in a more user friendly way. |
#
if [ ! -e "$1" ]; then echo "file $1 does not exist." exit 1 fi if [ -d "$1" ]; then echo -n "$1 is a directory that you may " if [ ! -x "$1" ]; then echo -n "not " fi echo "search." elif [ -f "$1" ]; then echo "$1 is a regular file." else echo "$1 is a special type of file." fi if [ -O "$1" ]; then echo 'you own the file.' else echo 'you do not own the file.' fi if [ -r "$1" ]; then echo 'you have read permission on the file.' fi if [ -w "$1" ]; then echo 'you have write permission on the file.' fi if [ -x "$1" -a ! -d "$1" ]; then echo 'you have execute permission on the file.' fi