Next Previous Contents

5. How can I get bash to do certain things, and why does bash do

things the way it does?

5.1 Why is the bash builtin `test' slightly different from /bin/test?

The specific example used here is [ ! x -o x ], which is false.

Bash's builtin `test' implements the Posix.2 spec, which can be summarized as follows (the wording is due to David Korn):

Here is the set of rules for processing test arguments.

The operators -a and -o are considered binary operators for the purpose of the 3 Arg case.

As you can see, the test becomes (not (x or x)), which is false.

5.2 Why does bash sometimes say `Broken pipe'?

If a sequence of commands appears in a pipeline, and one of the reading commands finishes before the writer has finished, the writer receives a SIGPIPE signal. Many other shells special-case SIGPIPE as an exit status in the pipeline and do not report it. For example, in:


      ps -aux | head

`head' can finish before `ps' writes all of its output, and ps will try to write on a pipe without a reader. In that case, bash will print `Broken pipe' to stderr when ps is killed by a SIGPIPE.

You can build a version of bash that will not report SIGPIPE errors by uncommenting the definition of DONT_REPORT_SIGPIPE in the file config-top.h.

5.3 When I have terminal escape sequences in my prompt, why does bash

wrap lines at the wrong column?

Readline, the line editing library that bash uses, does not know that the terminal escape sequences do not take up space on the screen. The redisplay code assumes, unless told otherwise, that each character in the prompt is a `printable' character that takes up one character position on the screen.

You can use the bash prompt expansion facility (see the PROMPTING section in the manual page) to tell readline that sequences of characters in the prompt strings take up no screen space.

Use the \[ escape to begin a sequence of non-printing characters, and the \] escape to signal the end of such a sequence.

5.4 If I pipe the output of a command into `read variable', why doesn't

the output show up in $variable when the read command finishes?

This has to do with the parent-child relationship between Unix processes.

Each element of a pipeline runs in a separate process, a child of the shell running the pipeline. A subprocess cannot affect its parent's environment. When the `read' command sets the variable to the input, that variable is set only in the subshell, not the parent shell. When the subshell exits, the value of the variable is lost.

Many pipelines that end with `read variable' can be converted into command substitutions, which will capture the output of a specified command. The output can then be assigned to a variable:


        grep ^gnu /usr/lib/news/active | wc -l | read ngroup

can be converted into


        ngroup=$(grep ^gnu /usr/lib/news/active | wc -l)

This does not, unfortunately, work to split the text among multiple variables, as read does when given multiple variable arguments. If you need to do this, you can either use the command substitution above to read the output into a variable and chop up the variable using the bash pattern removal expansion operators or use some variant of the following approach.

Say /usr/local/bin/ipaddr is the following shell script:


#! /bin/sh
host `hostname` | awk '/address/ {print $NF}'

Instead of using


        /usr/local/bin/ipaddr | read A B C D

to break the local machine's IP address into separate octets, use


        OIFS="$IFS"
        IFS=.
        set -- $(/usr/local/bin/ipaddr)
        IFS="$OIFS"
        A="$1" B="$2" C="$3" D="$4"

Beware, however, that this will change the shell's positional parameters. If you need them, you should save them before doing this.

This is the general approach -- in most cases you will not need to set $IFS to a different value.

5.5 I have a bunch of shell scripts that use backslash-escaped characters

in arguments to `echo'. Bash doesn't interpret these characters. Why not, and how can I make it understand them?

This is the behavior of echo on most Unix System V machines.

The bash builtin `echo' is modeled after the 9th Edition Research Unix version of `echo'. It does not interpret backslash-escaped characters in its argument strings by default; it requires the use of the -e option to enable the interpretation. The System V echo provides no way to disable the special characters; the bash echo has a -E option to disable them.

There is a configuration option that will make bash behave like the System V echo and interpret things like `\t' by default. Run configure with the --enable-usg-echo-default option to turn this on. Be aware that this will cause some of the tests run when you type `make tests' to fail.

There is a shell option, `xpg_echo', settable with `shopt' that will change the behavior of echo at runtime. Enabling this option turns on expansion of backslash-escape sequences.

5.6 Why doesn't a while or for loop get suspended when I type ^Z?

This is a consequence of how job control works on Unix. The only thing that can be suspended is the process group. This is a single command or pipeline of commands that the shell forks and executes.

When you run a while or for loop, the only thing that the shell forks and executes are any commands in the while loop test and commands in the loop bodies. These, therefore, are the only things that can be suspended when you type ^Z.

If you want to be able to stop the entire loop, you need to put it within parentheses, which will force the loop into a subshell that may be stopped (and subsequently restarted) as a single unit.

5.7 What about empty for loops in Makefiles?

It's fairly common to see constructs like this in automatically-generated Makefiles:


SUBDIRS = @SUBDIRS@
        ...
subdirs-clean:
        for d in $SUBDIRS; do \
                ( cd $$d && ${MAKE} ${MFLAGS} clean ) \
        done

When SUBDIRS is empty, this results in a command like this being passed to bash:


        for d in ; do
                ( cd $$d && ${MAKE} ${MFLAGS} clean )
        done

This is a syntax error. If the reserved word `in' is present, a word must follow it before the semicolon or newline. The language in the manual page referring to the list of words being empty refers to the list after it is expanded. There must be at least one word following the `in' when the construct is parsed.

The idiomatic Makefile solution is something like:


SUBDIRS = @SUBDIRS@
subdirs-clean:
        subdirs=$SUBDIRS ; for d in $$subdirs; do \
                ( cd $$d && ${MAKE} ${MFLAGS} clean ) \
        done


Next Previous Contents