Exit Status

Perhaps the only aspect of this syntax that differs from that of conventional languages like C and Pascal is that the "condition" is really a list of statements rather than the more usual Boolean (true or false) expression. How is the truth or falsehood of the condition determined? It has to do with a general UNIX concept that we haven't covered yet: the exit status of commands.

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. [1]

广告:个人专属 VPN,独立 IP,无限流量,多机房切换,还可以屏蔽广告和恶意软件,每月最低仅 5 美元

if checks the exit status of the last statement in the list following the if keyword. The list is usually just a single statement. If the status is 0, the condition evaluates to true; if it is anything else, the condition is considered false. The same is true for each condition attached to an elif statement (if any).

This enables us to write code of the form:

if command ran successfully
then
   normal processing
else
   error processing
fi

More specifically, we can now improve on the pushd function that we saw in the last chapter:

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
}

The call to cd is now inside an if construct. If cd is successful, it will return 0; the next two lines of code are run, finishing the pushd operation. But if the cd fails, it returns with exit status 1, and pushd will print a message saying that you haven't gone anywhere.

Notice that in providing the check for a bad directory, we have slightly altered the way pushd functions. The stack will now always start out with two copies of the first directory pushed onto it. That is because $PWD is expanded after the new directory has been changed to. We'll fix this in the next section.

You can usually rely on built-in commands and standard UNIX utilities to return appropriate exit statuses, but what about your own shell scripts and functions? For example, what if you wrote a cd function that overrides the built-in command?

Let's say you have the following code in your .bash_profile.

cd ( )
{
    builtin cd "$@"
    echo "$OLDPWD --> $PWD"
}

The function cd simply changes directories and prints a message saying where you were and where you are now. Because functions have higher priority than most built-in commands in the shell's order of command look-up, we need to make sure that the built-in cd is called, otherwise the shell will enter an endless loop of calling the function, known as infinite recursion.

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:

cd baddir
echo $?

causes the shell to print 1, while the following command causes it to print 0:

cd gooddir
echo $?

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.