ProTip: Forcing clean exit status on Linux (Bourne Shell) commands/scripts with “;:”

Often times in Linux (or BSD, Unix, etc) you want to trigger external commands, but don’t care if they succeed or fail. For example, in Debian, you can execute scripts and commands from /etc/network/interfaces using the [pre-]up and [post-]down directives. However, if something goes wrong and the command doesn’t exit cleanly, the interface action (ifup or ifdown) will fail. This can happen, for example, when adding a static route (ip route add ...) or alias address (ip addr add ...) that already exists ("RTNETLINK answers: File exists"). Another common scenario is using 'ethtool' to set interface parameters such as Tx/Rx queue sizes. If the ethtool command “fails” because the value is already set, that will cause the 'ifup' or 'ifdown' command to fail as well.

For example, on this particular NIC, ethtool reports that the Maximum RX ring length is 511 descriptors, with a current (default) size of 200 descriptors:

adam-desktop ~ # ethtool -g eth0
Ring parameters for eth0:
Pre-set maximums:
RX: 511
RX Mini: 0
RX Jumbo: 0
TX: 511
Current hardware settings:
RX: 200
RX Mini: 0
RX Jumbo: 0
TX: 511

To enhance NIC performance, you can use ethtool to max-out the RX descriptors, and the command succeeds (exit status 0):

adam-desktop ~ # ethtool -G eth0 rx 511
adam-desktop ~ # echo $?
0
adam-desktop ~ # ethtool -g eth0
Ring parameters for eth0:
Pre-set maximums:
RX: 511
RX Mini: 0
RX Jumbo: 0
TX: 511
Current hardware settings:
RX: 511
RX Mini: 0
RX Jumbo: 0
TX: 511

That’s all fine and dandy. But what but what if you repeat the 'ethtool -G eth0 rx 511' command?

adam-desktop ~ # ethtool -G eth0 rx 511
rx unmodified, ignoring
no ring parameters changed, aborting
adam-desktop ~ # echo $?
80

If you were running this ethtool command as part of a pre-up command in Debian’s /etc/network/interfaces, it would work fine the first time you ran 'ifup eth0'. However, if you then ran 'ifdown eth0' followed by another 'ifup eth0', then eth0 would fail to come up the second time around. That behavior fails the principle of least astonishment, and is the sort of thing that can cause outages and service failures.

However, simply appending ";:" to the command causes it to always exit with status 0 (success). When used in a the context of a pre-up interface directive, you can ensure that the interface will always come up cleanly. i.e.

adam-desktop ~ # ethtool -G eth0 rx 511;:
rx unmodified, ignoring
no ring parameters changed, aborting
adam-desktop ~ # echo $?
0

What’s happening here is that the semi-colon (;) is a command separator, and the colon (:) effectively means 'true'. The exit status of the first command (ethtool -G eth0 rx 511) is ignored, and only the exit status of the last command (:) is reported.

A more verbose example of this principle in action:

adam-desktop ~ # false
adam-desktop ~ # echo $?
1
adam-desktop ~ # true
adam-desktop ~ # echo $?
0
adam-desktop ~ # false;true
adam-desktop ~ # echo $?
0
adam-desktop ~ # true;false
adam-desktop ~ # echo $?
1