There are two deficiencies in bash that compensate each other.
When you write $'\0'
, that is internally treated identically to the empty string. For example:
$ a=$'\0'; echo ${#a}
0
That's because internally bash stores all strings as C strings, which are null-terminated — a null byte marks the end of the string. Bash silently truncates the string to the first null byte (which is not part of the string!).
# a=$'foo\0bar'; echo "$a"; echo ${#a}
foo
3
When you pass a string as an argument to the -d
option of the read
builtin, bash only looks at the first byte of the string. But it doesn't actually check that the string is not empty. Internally, an empty string is represented as a 1-element byte array that contains just a null byte. So instead of reading the first byte of the string, bash reads this null byte.
Then, internally, the machinery behind the read
builtin works well with null bytes; it keeps reading byte by byte until it finds the delimiter.
Other shells behave differently. For example, ash and ksh ignore null bytes when they read input. With ksh, ksh -d ""
reads until a newline. Shells are designed to cope well with text, not with binary data. Zsh is an exception: it uses a string representation that copes with arbitrary bytes, including null bytes; in zsh, $'\0'
is a string of length 1 (but read -d ''
, oddly, behaves like read -d $'\0'
).
for f in *
instead of parsingls
. – Jan 12 '13 at 16:01for i in $(ls)
is terribly stupid—I'm almost ashamed I used it as a bad example here. – slhck Jan 12 '13 at 16:04find … -exec
instead of looping around files, which works for most cases where you'd use such a for loop instead. Here,find
takes care of everything for you. – slhck Jan 12 '13 at 16:06find … -exec sh -c '…' {} ';'
. Here, withinsh -c
you can call the file as the argument and even use multiple lines. See Gilles' answer here for more: http://unix.stackexchange.com/a/9500/5893 – slhck Jan 12 '13 at 16:15