8

The following answer on Stack Overflow,

How can I repeat a character in bash?

imposes one plausible way of POSIX-ly repeating a single character, as follows. In this example let's use the equal sign 100 times:

printf %100s | tr " " "="

My problem is that I don't understand how it works, and I would prefer a straightforward explanation. Please refrain from comments like read the manual, I did so, and since I am not clever from it, I am asking this question as I've never used tr, nor seen such a printf statement.

13

In short, printf %100s will print 100 spaces, and tr " " "=" will convert those spaces to equal signs, effectively printing 100 equal signs.

Breaking it down:


printf is a shell built-in. It typically takes two or more arguments, the first of which is a "format string" and the rest will be used to fill up placeholders in that format string. Once that template is fully filled, it will print out the result. If there are more arguments left, it will start over, filling up more arguments and printing the resulting string.

The format string used for printf takes format specifications, which start with % and end with a single letter, so %d means an integer (using the decimal base, therefore "d"), %f means a floating-point number and %s means a string of characters. Characters other than letters after the % are modifiers for the format specification and, in particular, numbers are used to specify the requested length of the field on output. So %100s will format the string to have at least 100 characters, it will pad it with spaces and it will keep it aligned right (in other words, add spaces at the beginning of the string.)

If passed an extra argument, it would use it for that %s field, so for example printf %100s abc will print 97 spaces (to get to 100 total, considering the 3 in "abc") followed by the actual string "abc". But if no argument is given, then the format specification is filled with an empty or null argument (which is an empty string for %s, it would be 0 for %d, etc.) So that's the same as if an empty string was passed, such as printf %100s ''. The end result is that only the 100 character padding is printed.

So, putting it all together, printf %100s results in 100 spaces printed.


Now tr is a tool to translate characters from input to output. It takes two arguments, SET1 and SET2, each a set of characters, and then translates the first character of SET1 into the first of SET2, the second character of SET1 into the second of SET2 and so on. tr reads its input from stdin and writes it back to stdout (so it's very useful in pipelines like the one above.) tr will always translate all occurrences of that character in a given string.

For example, tr aeiou 12345 will translate lowercase vowels into the numbers 1 to 5 in that order, so it will translate "queueing" into "q52523ng" for example. You can also pass it character ranges, such as tr a-z A-Z to turn any lowercase letter into its corresponding uppercase one.

So tr " " "=" is simply translating spaces into equal signs throughout the string. The first space needs to be quoted to be recognized as an argument. The = doesn't actually need to be quoted, but doing so doesn't hurt. tr " " = would have worked the same.


Putting it all together, print 100 spaces, then translate each of them into equal signs.

Hopefully this explains it in enough detail, but if there's still something you don't understand, please leave a comment and I'll try to address that.

  • Just checking, would the following be more syntactically correct?: printf '%100s' ' ' | tr " " "=" – LinuxSecurityFreak Jan 5 at 8:50
  • 2
    @Vlastimil Actually printf '%100s' '', with an empty string... I updated the answer to include that. In this particular case, empty string or a single space wouldn't make a difference, but you can see a difference in printf '%sx\n', which is the same as printf '%sx\n' '' but different from printf '%sx\n' ' '. I hope that helps! – filbranden Jan 5 at 8:56
  • 1
    +1 for mention that tr operates on sets of characters. This is often left out. – Sergiy Kolodyazhnyy Jan 5 at 10:49
11

The printf command uses its first argument as a format for printing its subsequent arguments. printf %100s prints out its arguments padded to 100 characters wide, using spaces (on the left). There is no argument provided to format, so it formats the empty string once, and outputs 100 spaces. You can see that:

$ printf %100s | hexdump -C
00000000  20 20 20 20 20 20 20 20  20 20 20 20 20 20 20 20  |                |
*
00000064

(20 is the hex for a space; * means the previous line repeated)

The format strings use approximately the C Xprintf specifiers: %, an optional width to fit the formatted value into, and a type of format to use. s is string formatting, and strings are padded with spaces on the left by default. There could be multiple formats, or other literal parts: printf "a%10sb\n" hello prints

 a         xb.

tr replaces selected characters in its standard input with selected replacements, and prints the result to its standard output. tr " " "=" has a single character to be replaced - a space - and a single character to replace it with - an equals sign. It thus turns every space in its input into an =, and leaves the rest unchanged. You can try that as well:

$ tr " " "="
hello world
hello=world

(I typed the "hello world")

You could have multiple replacements: tr abc def turns a into d, b into e, c into f, and leaves the rest unchanged. Here it's just a single character, since that was what printf could cheaply generate.

The pipe | causes the output of the command on the left, printf %100s, to be used as the input to the command on the right, tr " " "=". That is, a hundred consecutive spaces are given to tr, and each one of them is replaced with an =, with the new string printed out.

printf %100s | tr " " "="
====================================================================================================
  • Just checking, would the following be more syntactically correct?: printf '%100s' ' ' | tr " " "=" – LinuxSecurityFreak Jan 5 at 8:53
  • 1
    Formatting a space and padding it with spaces is going to give the same output as formatting an empty string and padding with spaces, but it's not structurally equivalent. Perhaps it's more "correct" in terms of clarity about what's going on, but not syntactically, and they are different commands. – Michael Homer Jan 5 at 8:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.