This show has been flagged as Clean by the host. This series is dedicated to exploring little-known—and occasionally useful—trinkets lurking in the dusty corners of UNIX-like operating systems. The echo command is very useful—it prints the arguments given to it, followed by a newline character. (The newline is sometimes also called a linefeed character depending on who is writing or speaking, and has the ASCII decimal value 10.) It has many uses, either in a script or interactively on the command line. The echo utility is used to display text, the value of a variable, or the result of a pathname expansion. It can also feed text to another command in a pipeline. As useful as echo is, it should come as no surprise that it first appeared early on in Bell Laboratories' Second Edition UNIX 1 in 1972. This initial version accepted no options 2 —although the manual page doesn't explicitly say output is followed by a newline character, the description of writing "as a line" seems to imply it. In Seventh Edition UNIX, the manual page 3 makes that clear, and also features the addition of the -n option, which causes echo to print the arguments without a trailing newline character. Eighth Edition UNIX's echo 4 gained the -e option, which allows certain escape codes from the C programming language to be used. These variations caused differences in behavior between different versions of echo . Will running echo -n something on your system output the text "something" without a newline, or "-n something" followed by a newline? Things get even trickier when the command arguments include parameter or pathname expansions. If there are files named "-n" and "something" in the current directory, what does echo * output? Like the previous question, that depends on whether or not your version of echo treats -n as an option. You can't get around this ambiguity by quoting or escaping the "*", because that just causes echo to print a literal asterisk. Example using GNU utilities on Debian 12; both the "echo" utility and the "echo" builtin of bash recognize "-n" as an option. $ ls -1 -n something $ echo * something$ #Shell prompt is on the same line because "-n" was treated as an option to echo $ echo "*" * The solution was to create a new utility, which is the first UNIX Curio for today: printf . This command allows a user to print text similar to the way the identically-named function works in the C programming language. You run printf 5 followed by a format string, followed by zero or more arguments. No newline characters are printed unless specifically indicated by the format string or the arguments. To use printf to print "something" without a newline, that would just be printf something . This demonstrates that you don't need any arguments—in this example, the format string is just a set of regular characters to be displayed. If you wanted a newline character at the end, printf "something\n" would give you that. (In this case, the format string needs to be quoted so the "\n" isn't interpreted by the shell.) In addition to "\n" for a newline, you can also use "\a" for an alert (rings the terminal bell), "\b" for a backspace, "\f" for a formfeed, "\r" for a carriage return, "\t" for a horizontal tab, "\v" for a vertical tab, and "\\" to get a literal backslash. In addition to these special characters, any arbitrary byte can be included using a backslash followed by one to three octal digits; however, it might be difficult to predict what will be output because it can differ based on the character set the terminal is using. It is safer and more portable to stick to the pre-defined characters if possible. The real magic of the printf utility comes from using "conversion specifications" in the format string. Probably the simplest of these to explain is the "%s" conversion specification—it represents a string of any length. The command printf "Hi, %s, how are you?\n" followed by a list of names would print the greeting for each name, putting it in the place occupied by the "%s". $ printf "Hi, %s, how are you?\n" Alice Bob Carol Hi, Alice, how are you? Hi, Bob, how are you? Hi, Carol, how are you? The format string is reused as many times as needed to consume all of the arguments. Take, for example, the command printf "Hi, %s, have you met %s?\n" . If this is run with two name arguments, it would print the sentence on one line, using both names. If run with four name arguments, it would print the sentence twice, once with the first two names and again with the second two names. If you only gave it three names, the last "%s" conversion specification would be replaced with a null string. $ printf "Hi, %s, have you met %s?\n" Alice Bob Hi, Alice, have you met Bob? $ printf "Hi, %s, have you met %s?\n" Alice Bob Carol David Hi, Alice, have you met Bob? Hi, Carol, have you met David? $ printf "Hi, %s, have you met %s?\n" Alice Bob Carol Hi, Alice, have you met Bob? Hi, Carol, have you met ? Three other items can also be given in each conversion specification: flags, the field width, and the precision. The exact meanings of these depend on which type of conversion specifier character you are using. For "%s", using a "-" as the flag causes the text to be left-justified instead of the default right-justified, a field width causes the printed field to be at least as long as the number given, and a precision limits the number of bytes written from the string to the number given. $ #Example of %s with a precision value $ printf "Hi, %.3s, how are you?\n" Alice Bob Carol Hi, Ali, how are you? Hi, Bob, how are you? Hi, Car, how are you? $ #Example of %s with a field width $ printf "Hi, %8s, how are you?\n" Alice Bob Carol Hi, Alice, how are you? Hi, Bob, how are you? Hi, Carol, how are you? $ #Example of %s with a left-justify flag and a field width $ printf "Hi, %-8s, how are you?\n" Alice Bob Carol Hi, Alice , how are you? Hi, Bob , how are you? Hi, Carol , how are you? $ #Example of %s with a left-justify flag, a field width, and a precision $ printf "Hi, %-8.3s, how are you?\n" Alice Bob Carol Hi, Ali , how are you? Hi, Bob , how are you? Hi, Car , how are you? While "%s" is probably the most commonly-used conversion specification, others are available. A whole set of them are dedicated to printing integer values as a signed decimal, an unsigned decimal, an unsigned octal, or an unsigned hexadecimal number. These also can take flags, a field width, and a precision. I think the details and nuances of all this are too complex to clearly explain here, so I will just refer you to the POSIX "file format notation" specification 6 . Be aware that unlike the printf function in the C programming language, the printf utility is not obligated to accept conversion specifications for floating-point numbers. While some implementations might support this, scripts intended to be portable should limit themselves to the restricted set required by the POSIX standard (%d, %i, %o, %u, %x, %X, %c, and %s, plus %b and %% described below). Two more conversion specifications are worth mentioning. The first is only required by the standard for the printf utility, not the C function, and is "%b". This is the same as "%s", except that certain backslash escape sequences in the argument will be treated specially. This includes all the ones described above except for the one using octal digits to represent a byte. In an argument, this is instead represented by "\0" followed by one to three octal digits. An additional backslash escape sequence accepted is "\c"—this does not print anything itself, but causes printf to immediately halt output. The final conversion specification is "%%", which just outputs a literal "%". You can't use a bare "%" in the format string, because printf expects that to introduce a conversion specification. Be careful not to be tripped up by this when trying to print some value as a percentage. Example assuming that the hypothetical "/dev/batterycharge" file on your laptop outputs the battery charge level (42% in this case). As you can see, in some cases an error message might be displayed, but in others it might just behave in a way you didn't intend without complaining. GNU's "printf" utility and the "printf" builtin of bash both support "%e" as a conversion specification as an extension to POSIX. $ cat /dev/batterycharge 42 $ #Wrong $ printf "Your laptop's charge level is $(cat /dev/batterycharge)%.\n" bash: printf: `\': invalid format character Your laptop's charge level is 42$ #Shell prompt appears here from the error $ #Right $ printf "Your laptop's charge level is $(cat /dev/batterycharge)%%.\n" Your laptop's charge level is 42%. $ #Next one treats %e as the specifier, with the space and "l" as flags $ printf "Your laptop has $(cat /dev/batterycharge)% level of charge.\n" Your laptop has 42 0.000000e+00vel of charge. $ #Because no arguments were given, "0" was used for the value to convert Let's go back to the situation I was describing with echo —we have files named "-n" and "something" in the current directory and want to print all their names, separated by spaces. We could do that with printf "%s " * , which would not treat the "-n" as an option. However, the output might look a little weird because there wouldn't be a newline character at the end. We could insert a newline by using "%b" instead of "%s" and following the asterisk with a "\n\c" as the second argument. The "\c" is there to prevent the final space in the format string from being printed after the newline. $ ls -1 -n something $ printf "%s " * -n something $ #No newline was printed here $ printf "%b " * "\n" -n something $ #There's a newline, but also a spurious space before the shell prompt $ printf "%b " * "\n\c" -n something $ #No space before the shell prompt this