1. Introduction
During testing, development, or even administration, we sometimes need an executable file or command that performs no operation (no-operation, noop, nop) but is still able to run. Moreover, we might require a specific zero (success) or non-zero (failure) exit code from it as well.
In this tutorial, we’ll take a look at common executable files and commands that do nothing, as well as ways to create our own. First, we briefly touch upon system idling. Next, we move on to shells and ways to perform a no-operation within them. After that, we discuss purpose-built external executables. Finally, we implement our own ways to achieve the same effect.
We tested the code in this tutorial on Debian 11 (Bullseye) with GNU Bash 5.1.4. It should work in most POSIX-compliant environments.
2. Idling
Doing nothing may sound great, albeit in theory. Yet, just as a human still breathes, thinks, and lives even when static, machines idle actively:
$ sleep 666
[...]
Here, the sleep command, as implemented by the alarm() or nanosleep() system calls, prevents the execution of the caller for a given time, thereby idling. Importantly, the command’s time slot can still be reassigned to another thread.
Further, in Microsoft Windows, there’s the System Idle process, while UNIX and, by proxy, Linux reserve process ID (PID) 0 for the scheduler process, which creates and maintains idle threads for the kernel.
Either way, the function is the same: to fill any cycles of the central processing unit (CPU), which don’t have other assigned operations.
Importantly, in this energy-saving mode, the CPU waits for an interrupt instead of performing specific actions.
Still, under Linux, we can also manually simulate idling via instructions that perform no operation. Let’s look at some, along with example use cases.
3. Do Nothing With Bash
There are several Bash built-ins that fit our needs. Moreover, all of them are in the POSIX standard. Critically, we discuss options, which don’t act like sleep, but rather like an active operation that has no side effects.
To check the exit code in each case, we use the special $? question mark variable.
3.1. : Colon Null Utility
The : colon built-in, otherwise known as the null utility, serves to do nothing:
$ if [ : ]; then :; fi
Here, we use it as both a condition and action in a one-line if statement. Notably, we can also use the utility to only expand command arguments:
$ : argument1 argument2
The : colon null utility returns a 0 status code.
3.2. true and false
The true and false built-ins both perform the same function with a different exit status:
- true returns 0, i.e., success
- false returns 1, i.e., failure
So, one use case for both is another way to return a value from Bash functions:
$ func1() { false; }
$ func1
$ echo $?
1
$ func0() { true; }
$ func0
$ echo $?
0
Here, we define and call the func1() and func0() functions, showing their return value.
Depending on the commands before them, true and false can also correct specific situations where we want a given exit status without burdening the CPU.
Basically, true* is more or less equivalent to *: colon. On the other hand, we can consider another built-in to be a substitute for false.
3.3. test Built-In
Although a potentially complex utility for expression evaluation, the test built-in, called without any arguments, returns 1 just like false:
$ test
$ echo $?
1
Unlike false, test can’t be used to simply expand arguments. Further, the [ opening bracket built-in can’t be used alone instead of test in this case, as it requires a ] closing bracket:
$ [ ]
$ echo $?
1
Adding the required syntax works fine.
3.4. Other Built-Ins
Other built-ins without side effects can also return 0 or 1 when called without any or with certain arguments:
$ printf ''; echo $?
0
$ eval; echo $?
0
One can argue that suppressing the output of such commands or even a non-existent command can leave us with the same effect as the built-ins we discussed previously.
However, unlike others, true, false, and : are purpose-built to be safe and to minimize CPU usage:
$ time for c in {1..1000}; do false; done
real 0m0.002s
user 0m0.002s
sys 0m0.002s
$ time for c in {1..1000}; do error 2>/dev/null; done
real 0m0.463s
user 0m0.349s
sys 0m0.201s
Here, we run false and the non-existent error commands in a loop for 1000 cycles. Consequently, time shows that the latter takes hundreds of times longer than the former.
Now, let’s turn to solutions that don’t require a shell.
4. /bin/true and /bin/false
While true and false are both Bash built-ins, the GNU coreutils package also contains equivalent tools.
Because of this, we have the ability to invoke /bin/true and /bin/false outside a shell or even instead of one:
$ cat /etc/passwd
[...]
userx:x:1010:1010:,,,::/bin/false
Here, we see an entry in the /etc/passwd file for a user that has /bin/false as their shell.
However, this versatility comes at a price:
$ time for c in {1..1000}; do false; done
real 0m0.002s
user 0m0.002s
sys 0m0.002s
$ time for c in {1..1000}; do /bin/false; done
real 0m0.565s
user 0m0.462s
sys 0m0.191s
Similar to our earlier example, the /bin/false external executable is much slower than the built-in mainly due to the additional exec calls.
5. Custom No-Operation Dummy Commands and Executables
Naturally, we can create executable scripts, which include any of the commands and binaries we discussed so far. On the other hand, we can leverage third-party options to write our own tools.
5.1. Interpreters
Most interpreters have options to run one-liners. Using those, we can create a program that does nothing with a single command:
$ python -c ''; echo $?
0
$ python -c 'exit(1);'; echo $?
1
$ perl -e ''; echo $?
0
$ perl -e 'exit 1;'; echo $?
1
$ ruby -e ''; echo $?
0
$ ruby -e 'exit(1);'; echo $?
1
In all cases, the added bonus is being able to change the exit code at will.
Yet, we still have the performance problem representative for tools that aren’t purpose-built:
$ time for c in {1..1000}; do /bin/true; done
real 0m0.556s
user 0m0.450s
sys 0m0.191s
$ time for c in {1..1000}; do perl -e ''; done
real 0m1.283s
user 0m0.968s
sys 0m0.409s
To try and reduce this, let’s turn to a compiled language.
5.2. Compiling
The simplest C programming language program:
$ cat ret0.c
int main(){return 0;}
$ gcc ret0.c -o ret0
$ ./ret0; echo $?
0
$ cat ret1.c
int main(){return 1;}
$ gcc ret1.c -o ret1
$ ./ret1; echo $?
1
However, we get similar performance with our non-standard utility as we did with /bin/true and /bin/false. The only benefit is the control our implementations give over the exit code.
6. Summary
In this article, we talked about built-ins, commands, binaries, and other executables that do nothing.
In conclusion, while there are many options to run code without side effects that returns a given exit status, choosing the fastest and purpose-built one for a given context is usually the best choice.