1. Introduction
The null, NULL, \0, or
In this tutorial, we explore the NULL character and ways to insert it and display it in the terminal. First, we discuss the important role of zero and NULL in general. After that, we talk about printing and displaying NULL. Finally, we tackle ways to type NULL with the keyboard.
We tested the code in this tutorial on Debian 12 (Bookworm) with GNU Bash 5.1.4. It should work in most POSIX-compliant environments unless otherwise specified.
2. Zero and the NULL Character
Zero is the only unsigned number. NULL is the zeroth (0) character in the ASCII table as well as many other encodings and character sets. Since, as a control character, it doesn’t have an actual visual representation, we can see NULL in many forms:
- null: typical character name
- NULL: capitalized use in some specific cases and as a distinguishment
-
: angle bracket character notation - ␀ (NUL): Unicode U+2400 NUL marker
- \000, \x00, \u0000: escape sequences**
- \0: ANSI-C escape code
- ^@: caret notation
So, NULL is the actual value of zero (0). Importantly, unlike NULL, the 0 zero character has an ASCII value of 48.
Let’s go over the uses of NULL.
2.1. No Operation (NOP)
A no operation (NOP, no-operation, noop, nop) is an instruction in a given system to do nothing for a given amount of time. Its use can vary depending on the purpose of the system:
- delay or sleep
- signal
- pause
- synchronization
- code filler
Historically, one of the first functions for NULL was as a NOP signal. This way, time could be acquired for further processing in cases of older and slower peripherals or hardware.
Although the actual nop instruction usually has a different opcode, a NULL was able to invoke it, e.g., over a network or serial port.
2.2. Non-Value
In a philosophical way, nothing exists if nothing doesn’t exist.
The idea and character NULL have a place as the opposite of non-NULL values, i.e., everything else. In a binary system, this is critical to establish differences between three zero-value types:
- undefined, i.e., NULL (ASCII 0) pointer
- empty, i.e., NULL (ASCII 0) value
- zero character, i.e., 0 (ASCII 48) value
In fact, NULL plays a critical role in both of the first two, but also as a contrast for the third.
On the one hand, variables are pointers, so their address points to a memory segment to store any values. Thus, pointing to a bad address like NULL effectively means the variable has an undefined memory segment, so it can’t hold values:
NULL +------------+
____________
0001 |____________|
0002 |____________|
0003 |____________|
... ...
On the other hand, empty variables take up memory, but it’s entirely composed of random data or filled with NULL characters. In practice, this depends on whether the language holds a separate mapping of assignments (initializations) as opposed to checking the value itself and reading it until a NULL. Finally, a variable value of zero is indistinguishable from a NULL unless we have another source of information.
In this context, verifying a given variable or object reference is NULL can be hard to do, as it depends on the system, language, and current conditions.
For example, an empty string can be NULL or just undefined:
$ string=''
$ [[ $string == $'\0' ]] && echo 'NULL string.'
NULL string.
$ [[ $undefined == $'\0' ]] && echo 'Undefined string.'
Undefined string.
In Bash, empty, uninitialized, and NULL variables are all the same, but typed languages can have a separate way of representing each.
Yet, in some cases, like the Structured Query Language (SQL) and JavaScript, NULL (and null) is just a maker for non-value instead of the actual NULL character.
2.3. String Terminator
Strings are sequences of bytes. When it comes to strings in general, NULL is the string terminator in most languages.
For example, let’s explore a minimalistic C code:
#include <stdio.h>
int main() {
char str[] = "string";
for (int i = 0; i <= 6; i++) {
printf("%d is %c = ASCII %d\n", i, str[i], str[i]);
}
return 0;
}
Here, we initialize the str array with the text string. After that, *we iterate from the str[0] start to the str[6] end of the text, printing the current [%c]haracter, its [i]ndex, and ASCII value*.
Let’s use gcc to compile the code and then run it:
$ gcc str.c -o str.o
$ ./str.o
0 is s = 115
1 is t = 116
2 is r = 114
3 is i = 105
4 is n = 110
5 is g = 103
6 is = 0
Similar to EOF, NULL is just the terminator and is not considered part of the string, despite being allocated with it. Because of this special function, overwriting some buffer byte with NULL or vice-versa can lead to serious exploits.
Further, some UTF encodings use the two bytes 0xC0 and 0x80 to encode another NULL, so the ASCII and Unicode 0x00 is left free as a terminator.
2.4. Speed and Efficiency of Zeroes
The NULL character special functions are in part due to the importance of the zero (0) value in computing.
In general, at the lowest level of processor instructions and Assembly language, operations with zeroes are very short and fast. For instance, the zero flag (ZF) is associated with many single-byte instructions. In fact, exit status codes in Linux are related to such instructions for checking problematic conditions.
Further, some central processing unit (CPU) types have a special code for the NULL address, so even NULL pointers get special treatment.
Thus, many conditions are indicated by a zero, although the use of a NULL value or object as a signal for a condition isn’t part of best practices.
3. Print and Display NULL
After understanding the place of NULL, let’s see how we can insert it in the terminal or shell when needed.
3.1. Display Special Characters
Naturally, as a special character without a visual glyph, we need to use an alternative notation to show NULL:
$ perl -e 'for(my $c = 0; $c < 256; $c++) { print(sprintf("%c is %d %x\n", $c, $c, $c)); }' | cat --show-nonprinting
^@ is 0 0
^A is 1 1
^B is 2 2
^C is 3 3
[...]
This snippet in Perl prints all characters in the ASCII table while we add a pipe to cat is used to –show-nonprinting (-v) characters in caret notation. Caret notation is very common in modern shells for representing non-printable characters.
In this case, we can see the first character is NULL, displayed as a combination of ^ caret and @ asperand. We use cat to get the ^@ caret notation of NULL in most examples, as the character would be invisible otherwise.
3.2. Using echo and printf
Both echo (with -e) and printf can print special ANSI escape codes like the one for NULL (\0):
$ echo -e '\0' | cat --show-nonprinting
^@
$ printf '\0' | cat --show-nonprinting
^@
However, we can directly use ANSI-C quoting like $’\0′ in more than one context as well.
3.3. Using tr and perl for Substitutions
Sometimes, we might need to replace certain delimiters or other special function characters with NULL. To do this, we can use several standard tools.
Of course, one of the main functions of the basic tr command is for replacements:
$ printf 'Line 1.\nLine 2.\n' | tr '\n' '\0' | cat --show-nonprinting
Line 1.^@Line 2.^@
In this case, *we use printf to output a multiline string and then pipe that to tr to replace each [\n]ewline with a \0 NULL*. Like before, we use cat to see the latter.
Similarly, we can employ perl with a regular expression (regex) for the same purpose:
$ printf 'Line 1.\nLine 2.\n' | perl -pe 's/\n/\0/g;' | cat --show-nonprinting
Line 1.^@Line 2.^@
Here, we [-e]xecute a Perl one-liner that iterates over the input and [p]rints each line. Then, *we perform a [g]lobal [s]ubstitution of \n with \0*.
3.4. Input and Output Processing
Many tools have the built-in ability to use NULL instead of a newline or others as a terminator in their output or process input as if NULL is the terminator:
- find uses printf0 to append NULL to each output path
- sort uses –zero-terminated (-z) to set NULL as the line delimiter
- Bash uses $’\0′ as the value of IFS to set NULL as the internal field separator
- perl uses -0 to set NULL as the record separator
- sed uses –null-data (-z) to set NULL as the line separator
- grep uses –null-data (-z) to append NULL instead of a newline to each output line
- grep uses –null (-Z) to set NULL as the input separator
- awk uses “\x00” as the value of FS to set NULL as the field separator
- xargs uses –null (-0) to use NULL as the only input separator
- read, readarray, and mapfile use -d ” (-d $’\0′) to set NULL as the delimiter
- cut uses –delimiter ” (-d $’\0′) to set NULL as the delimiter
Normally, these features exist for universal path processing even when encountering files with special names. In fact, only NULL and / forward slash are disallowed in filesystem object names.
4. Typing NULL
There are different ways to type special characters. Furthermore, many factors can dictate how we might be able to type NULL in a given context:
- hardware
- driver
- kernel
- terminal
- terminal emulator
- shell
- environment
Still, one of the most common methods to use the keyboard for special character input is by using Control as a prefix in a two-key combination. Actually, the caret notation stems from the fact that Ctrl is usually represented by a ^ caret.
Thus, we might be able to insert NULL with a simple combination:
$ [Ctrl+2]^@
Since the 2 key usually also contains @, and we already saw the latter as part of the NULL representation, Ctrl+2 translates to ^@.
In the same way, Ctrl+C is equivalent to ^C or the EOT (End-Of-Transmission) character, meaning a SIGINT interrupt.
However, due to complexities with key and combination detection, we might have to try different ways to type NULL:
- Ctrl+V, Ctrl+Shift+2
- Ctrl+V, Shift+2 (@) without releasing Ctrl
- Ctrl+Shift+V, 2 without releasing Ctrl+Shift
- Ctrl+Shift, release Shift, press Shift, V, 2
Usually, if the above methods don’t work due to the shell or environment, we can perform a basic experiment by running an infinite loop and typing Ctrl+Space:
$ while true; do true; done
[Ctrl+Space]^@
Evidently, some terminals or a terminal user interface (TUI) like tmux or screen might also accept Ctrl+Space or custom combinations.
5. Summary
In this article, we talked about the special place of zero and NULL in Linux and computing, as well as ways to display, output, and type the NULL character.
In conclusion, NULL is critical to many functions, and we might not always have a direct way to type it, but should be able to output and display it in many settings.