How to print strings separated by TAB in bash?
Where the whitespace between the two is actually 5 spaces (as per selecting the output with mouse in Putty). I have also tried using CTRL+V and pressing TAB when typing the command, with the same result. What is the correct way to force tab being printed as tab, so I can select the output and copy it to somewhere else, with tabs? And the secondary question: why is bash expanding tabs into spaces? Update: Apparently, this is a problem of Putty: https://superuser.com/questions/656838/how-to-make-putty-display-tabs-within-a-file-instead-of-changing-them-to-spaces
Despite that fact that you already know that you have an issue with your terminal: Bash by itself interprets $’\t’ as tabulator. So you can always concatenate strings like this — for example as assignment: v=’This is’$’\t»a test’ And the print it literally, e.g. printf ‘%s’ «$v»
3 Answers 3
the whitespace between the two is actually 5 spaces.
No, it’s not. Not in the output of echo or printf .
$ echo -e 'foo\tbar' | od -c 0000000 f o o \t b a r \n 0000010
What is the correct way to force tab being printed as tab, so I can select the output and copy it to somewhere else, with tabs?
This is a different issue. It’s not about the shell but the terminal emulator, which converts the tabs to spaces on output. Many, but not all of them do that.
It may be easier to redirect the output with tabs to a file, and copy it from there, or to use unexpand on the output to convert spaces to tabs. (Though it also can’t know what whitespace was tabs to begin with, and will convert all of it to tabs, if possible.) This of course would depend on what, exactly, you need to do with the output.
I meant that when I try to select the output, it is being treated as 5 spaces. Thanks for the ‘od -c’ to verify the contents of the command output.
@Asu I think he understands that. His solution is to get the output via other means since the terminal emulator is not guaranteed to leave tabs as tabs when you select them in the window. However, I just checked and while putty, xterm, and konsole convert tabs to spaces, urxvt and gnome-terminal do not. So, another solution is to switch terminals.
@JoL Yes, that’s the conclusion I just came to a minute ago, and I think it would be the accepted answer if somebody cares to post it as such.
@Asu, yeah, I thought about just working around the issue manually. It would be annoying to have to do that, but then I admit I hadn’t realized that there are terminal emulators that do support copying tabs. Changing to one that does, would of course be a much better solution!
Like ilkkachu said, this isn’t an issue with bash, but with the terminal emulator which converts tabs to spaces on output.
Checking different terminals, putty, xterm, and konsole convert tabs to spaces, while urxvt and gnome-terminal do not. So, another solution is to switch terminals.
In printf ‘%s\t%s\n’ foo bar , printf does output foobar .
f , o , b , a and r are single-width graphical characters.
Upon receiving those characters, the terminal will display a corresponding glyph and move the cursor one column to the right, unless it’s already reached the right edge of the screen (paper in original tele-typewriters)), in which case it may feed a line and return to the left edge of the screen (wrap) or just discard the character depending on the terminal and how it’s been configured.
and are two control characters. (aka newline) is the line delimiter in Unix text, but for terminals, it just feeds a line (move the cursor one position down). So the terminal driver in the kernel will actually translate it to (return to the left edge of the screen), (cursor down) ( stty onlcr generally on by default).
tells the terminal to move the cursor to the next tab stop (which on most terminals are 8 positions apart by default but can also be configured to be set anywhere) without filling the gap with blanks.
So if those characters are sent to a terminal with tab stops every 8 columns whilst the cursor is at the start of an empty line, that will result in:
printed on the screen at that line. If they are sent whilst the cursor is in third position in a line that contains xxxxyyyyzzzz , that will result in:
On terminals that don’t support tabulation, the terminal driver can be configured to translate those tabs to sequences of spaces. ( stty tab3 ).
The SPC character, in original tele-typewriters would move the cursor to the right, while backspace ( \b ) would move it to the left. Now in modern terminals, SPC moves to the right and also erases (writes a space character as you’d expect). So the pendant of \b had to be something newer than ASCII. On most modern terminals, it’s actually a sequence of characters: , [ , C .
There are more escape sequences to move n characters left, right, up, down or at any position on the screen. There are other escape sequences to erase (fill with blank) parts of lines or regions of the screen, etc.
Those sequences are typically used by visual applications like vi , lynx , mutt , dialog where text is written at arbitrary positions on the screen.
Now, all X11 terminal emulators and a few other non-X11 ones like GNU screen let you select areas of the screen for copy paste. When you select a part of what you see in the vi editor, you don’t want to copy all the escape sequences that have been used to produce that output. You want to select the text you see there.
Which simulates an editor session where you enter abC , go back to the beginning, replace ab with AC , C with B , move to the next tab stop, then one more column to the right, then two columns to the left, then enter D .
That is, ABC , a 4 column gap and D .
If you select that with the mouse in xterm or putty , they will store in the selection ABC , 4 space characters and D , not abCACB[CD .
What ends up in the selection is what has been sent by printf but post-processed by both the terminal driver and the terminal emulator.
For other kinds of transformation, see the ( e followed by a combining acute accent) changed to ( é the pre-composed form) by xterm .
Or echo abc that ends up being translated to ABC by the terminal driver before sending to the terminal after a stty olcuc .
Now, , like is one of those few control characters that are actually sometimes found in text files (also in MSDOS text files, and sometimes for page break).
So some terminal emulators do choose to copy them when possible in the copy-paste buffers to preserve them (that’s generally not the case of nor though).
For instance, in VTE-based terminals like gnome-terminal , you may see that, when you select the output of printf ‘a\tb\n’ on an empty line, gnome-terminal actually stores a\tb in the X11 selection instead of a , 7 spaces and b .
But for the output of printf ‘a\t\bb\n’ , it stores a , 6 spaces and b , and for printf ‘a\r\tb\n’ , a , 7 spaces and b .
There are other cases where the terminals will try to copy the actual input, like when you select two lines after running printf ‘a \nb\n’ where that invisible trailing space will be preserved. Or when selecting two lines doesn’t include a LF character when the two lines result from wrapping at the right margin.
Now, if you want to store the output of printf into the CLIPBOARD X11 select, best is to do it directly like with:
printf 'foo\tbar\n' | xclip -sel c
Note that when you paste that in xterm or most other terminals, xterm actually replaces that \n with \r because that’s the character xterm sends when you press Enter (and the terminal driver may translate it back to \n ).
Echo Tab Characters in Bash Script
This write-up explains how to echo one or multiple tab characters when using a Bash script.
Echo Tab Characters in Bash Script
The echo command is simple: it prints whatever is passed to the terminal. Normally, if you save a variable as something like:
If you try to print out this variable with the command echo ‘[‘$example’]’ , you will get the following output.
This is not what we have input or saved to the variable. In the above example, we used the tab character twice, yet all we see is a single space.
It should be noted that this behavior is not consistent across all shells. For example, running the same script in the zsh shell will result in the following output, which we expect to get.
Coming back to Bash scripts, we have a few ways around this.
Use the -e Flag
The -e flag means enable interpretation of backslash escapes . If you were writing the same example above, you would write it as follows.
example='\t\t'testing echo -e '['$example']'
The \t part represents a tab. Hence, as we wanted to introduce two tabs, we wrote it as \t\t .
Once the -e flag is passed, the \t symbol is interpreted as a tab and displayed as required.
If the -e flag is not used, as in the following case, then the \t will be read as literal strings and will not be processed as tabs.
example='\t\t'testing echo '['$example']'
It should be noted that passing the flag is not POSIX; hence, this cannot be used portably, such as in make , in which the flag is not implemented. Hence, we will discuss a few alternatives, including the almost universal printf .
Of course, this part slightly deviates from what we started with, as we will abandon echo entirely.
Use Double Quotes
This is arguably the simplest solution and does not require any explanation other than its syntax. You would write the command as follows.
example=' 'testing echo "[$example]"
Which would give the result:
Use printf Instead
This is the most portable of the three solutions and the most recommended. echo has many implementations and versions, but printf is mostly the same.
Even if it is implemented separately anywhere, it is implemented very similarly. We can see this as any variation of the above methods would result in the same output when called using zsh but different when called using bash .
The syntax for using printf is defined below.
example=' 'x printf '%s\n' "[$example]"
As a general recommendation, echo works fine as long as the output needed is a simple text like hello , but for anything complex, using echo can get tricky because of how different its behavior can be across shells. In all such cases (special characters, etc.), it is recommended to use printf instead.
Husnain is a professional Software Engineer and a researcher who loves to learn, build, write, and teach. Having worked various jobs in the IT industry, he especially enjoys finding ways to express complex ideas in simple ways through his content. In his free time, Husnain unwinds by thinking about tech fiction to solve problems around him.
Related Article — Bash Echo
Echo tab characters in bash script
will echo ‘space tab space newline’ ( -e means ‘enable interpretation of backslash escapes’):
$ echo -e ' \t ' | hexdump -C 00000000 20 09 20 0a | . .|
That’s because echo -e is not POSIX and make calls /bin/sh which is normally not the program use use interactively, and normally hasn’t -e implemented. For portability, use printf ‘\t’ instead.
In Darwin (macOS), the man page for the echo command does not mention the option -e . However, this option is accepted.
There are multiple different versions of the echo command. There’s /bin/echo (which may or may not be the GNU Coreutils version, depending on the system), and the echo command is built into most shells. Different versions have different ways (or no way) to specify or disable escapes for control characters.
printf , on the other hand, has much less variation. It can exist as a command, typically /bin/printf , and it’s built into some shells (bash and zsh have it, tcsh and ksh don’t), but the various versions are much more similar to each other than the different versions of echo are. And you don’t have to remember command-line options (with a few exceptions; GNU Coreutils printf accepts —version and —help , and the built-in bash printf accepts -v var to store the output in a variable).
res=' 'x # res = "\t\tx" printf '%s\n' "[$res]"
And now it’s time for me to admit that echo will work just as well for the example you’re asking about; you just need to put double quotes around the argument:
as kmkaplan wrote (two and a half years ago, I just noticed!). The problem with your original commands:
res=' 'x # res = "\t\tx" echo '['$res']' # expect [\t\tx]
isn’t with echo ; it’s that the shell replaced the tab with a space before echo ever saw it.
echo is fine for simple output, like echo hello world , but you should use printf whenever you want to do something more complex. You can get echo to work, but the resulting code is likely to fail when you run it with a different echo implementation or a different shell.