How do you type a tab in a bash here-document?
The definition of a here-document is here: http://en.wikipedia.org/wiki/Here_document
How can you type a tab开发者_JS百科 in a here-document? Such as this:
cat > prices.txt << EOF
coffee\t$1.50
tea\t$1.50
burger\t$5.00
EOF
UPDATE:
Issues dealt with in this question:
- Expanding the tab character
- While not expanding the dollar sign
- Embedding a here-doc in a file such as a script
TAB="$(printf '\t')"
cat > prices.txt << EOF
coffee${TAB}\$1.50
tea${TAB}\$1.50
burger${TAB}\$5.00
EOF
You can embed your here doc in your script and assign it to a variable without using a separate file at all:
#!/bin/bash
read -r -d '' var<<"EOF"
coffee\t$1.50
tea\t$1.50
burger\t$5.00
EOF
Then printf
or echo -e
will expand the \t
characters into tabs. You can output it to a file:
printf "%s\n" "$var" > prices.txt
Or assign the variable's value to itself using printf -v
:
printf -v var "%s\n" "$var"
Now var
or the file prices.txt
contains actual tabs instead of \t
.
You could process your here doc as it's read instead of storing it in a variable or writing it to a file:
while read -r item price
do
printf "The price of %s is %s.\n" $item $price # as a sentence
printf "%s\t%s\n" $item $price # as a tab-delimited line
done <<- "EOF"
coffee $1.50 # I'm using spaces between fields in this case
tea $1.50
burger $5.00
EOF
Note that I used <<-
for the here doc operator in this case. This allows me to indent the lines of the here doc for readability. The indentation must consist of tabs only (no spaces).
For me, I type ctrl-V followed by ctrl-I to insert a tab in the bash shell. This gets around the shell intercepting the tab, which otherwise has a 'special' meaning. Ctrl-V followed by a tab should work too.
When embedding dollar signs in a here document you need to disable interpolation of shell variables, or else prefix each one with a backslash to escape (i.e. \$
).
Using your example text I ended up with this content in prices.txt:
coffee\t.50
tea\t.50
burger\t.00
because $1
and $5
are not set. Interpolation can be switched off by quoting the terminator, for example:
cat > prices.txt <<"EOF"
As others have said, you can type CTRL-V then tab to insert a single tab when typing.
You can also disable bash tab-completion temporarily, for example if you want to paste text or if you want to type a long here-doc with lots of tabs:
bind '\C-i:self-insert' # disable tab-completion
# paste some text or type your here-doc
# note you don't use "\t" you just press tab
bind '\C-i:complete' # reenable tab-completion
EDIT: If you're on a Mac and use iTerm 2, there is now a "Paste with literal tabs" option that allows pasting code with tabs onto the bash command line.
I note that the correct answer has already been given, but I am attempting to summarize into a more succinct answer.
1. There is nothing to prevent you from having literal tab characters in a here document.
To type a literal tab at the Bash prompt, you need to escape it. The escape character is ctrl-V (unless you have custom bindings to override this).
$ echo -n 't<ctrl-v><tab>ab' | hexdump -C
00000000 74 09 61 62 |t.ab|
00000004
In most any programmer's editor, it should be trivial to insert a literal tab character (although some editors might want escaping, too. In Emacs, ctrl-Q TAB inserts a literal tab).
For legibility, it might be better to use some sort of escape instead of a literal tab character. In Bash, the $'...' string syntax is convenient for this.
2. To prevent variable expansion, quote all dollar signs, or put the here doc terminator in quotes.
$ hexdump -C <<HERE
> t<ctrl-v><tab>\$ab
HERE
00000000 74 09 24 61 62 0a |t.$ab.|
00000006
$ hexdump -C <<'HERE'
> t<ctrl-v><tab>$ab
HERE
00000000 74 09 24 61 62 0a |t.$ab.|
00000006
In this context, it doesn't matter whether you use single or double quotes.
3. I am not sure I understand this subquestion. The purpose of a here document is to embed it in a script. The previous example illustrates how to pass a here document to hexdump in a script, or at the command line.
If you want to use the same here document multiple times, there is no straightforward way to do that directly. The script could write a here document to a temporary file, then pass that temporary file to multiple commands, then erase the temporary file. (Take care to use trap
to remove the temporary file also in case the script is interrupted.)
You could also put the contents of the here document in a variable, and interpolate that.
# Note embedded newlines inside the single quotes,
# and the use of $'...\t...' to encode tabs
data=$'coffee\t$1.50
tea\t$1.50
burger\t$5.00'
# Run Word Count on the data using a here document
wc <<HERE
$data
HERE
# Count number of tab characters using another here document with the same data
grep -c $'\t' <<HERE
$data
HERE
You could equivalently use echo -E "$data" | wc; echo -E "$data" | grep -c $'\t'
but using echo is not very elegant and might be less portable (though if your target is bash, all echos should be the same. If your target is Bourne shell in general, you might also spend an external process for each echo).
Here's a shorter version of Dennis Williamson's answer. Inspiration from here: http://tldp.org/LDP/abs/html/here-docs.html.
#!/bin/bash
var=$(echo -e "$(cat <<"EOF"
coffee\t$1.50
tea\t$1.50
burger\t$5.00
EOF
)")
echo "$var"
Tabs that are pasted into a heredoc vanish, because bash
is still interpreting them as special characters marking the start of an auto-completion sequence/request.
If you want to quickly paste a heredoc in the current shell, you can do this by disabling auto-completion for the life of the current shell.
Here's what happens with normal auto-completion if you paste a heredoc containing tabs:
$ cat <<EOF
TABAFTERTABBEFORE
TABAFTERTABBEFORE
TABAFTERTABBEFORE
TABAFTERTABBEFORE
TABAFTERTABBEFORE
EOF
TABAFTERTABBEFORE
TABAFTERTABBEFORE
TABAFTERTABBEFORE
TABAFTERTABBEFORE
TABAFTERTABBEFORE
Run this command:
bind "set disable-completion on"
Paste again and your tabs are retained:
$ cat <<EOF
> TABAFTER TABBEFORE
> TABAFTER TABBEFORE
> TABAFTER TABBEFORE
> TABAFTER TABBEFORE
> TABAFTER TABBEFORE
> EOF
TABAFTER TABBEFORE
TABAFTER TABBEFORE
TABAFTER TABBEFORE
TABAFTER TABBEFORE
TABAFTER TABBEFORE
Re: subquestion #3, I read this question as:
"[W]hat if I wanted to[...] store the here-doc commented in the same file as the script for later reference?"
Use the script name as the output of the here doc, appending rather than replacing, assuming the executor also has write permissions. Shell comments are not interpreted during a here doc block.
_thisline=${LINENO}
cat <<EOF >>$0
#====== $(date) =========
#On this run, these variable values were used as of line ${_thisline}: A=${A}, B=${B}, B=${C}
EOF
In a similar way you can use a here doc to write out a new script expanding variables to values, exec it, and then you have an exact record of what was run rather having to trace the code.
If you want to use tabs for the file indentation and for the heredoc: You just need to separate the tabs of the indentation, from the tabs of the document with a whitespace:
try_me() {
# @LinuxGuru's snippet; thanks!
sed 's/^ //g' >> tmp.conf <<-EOF
/var/log/nginx/*log {
daily
rotate 10
missingok
notifempty
compress
sharedscripts
postrotate
/bin/kill -USR1 $(cat /var/run/nginx.pid 2>/dev/null) 2>/dev/null || :
endscript
}
EOF
}
try_me
The only drawback is that the not-indented lines will look a little weird; they have a leading whitespace char on the script
/var/log/nginx/*log
}
However, that won't be there on the resulting file (sed 's/^ //g'
instead of cat
)
One simple and direct solution to the original problem is to use the $(echo $'...') idiom:
cat > prices.txt << EOF
$(echo $'coffee\t$1.50')
$(echo $'tea\t$1.50')
$(echo $'burger\t$5.00')
EOF
If you use a tool like sed and quote the delimiter (EOF), things can get simpler:
sed 's/\\t/\t/g' > prices.txt << 'EOF'
coffee\t$1.50
tea\t$1.50
burger\t$5.00
EOF
- Quoting EOF prevents parameter (dollar sign) expansion.
- sed converts '\t' into tab characters.
- If you have patters like
\\t
in your here doc, then you would need a more complex sed invocation such as:sed -e 's/\\t/\t/g' -e 's/\\\t/\\t/g'
.
Use @EOF and it will preserve tabs.
cat >> /etc/logrotate.d/nginx <<@EOF
/var/log/nginx/*log {
daily
rotate 10
missingok
notifempty
compress
sharedscripts
postrotate
/bin/kill -USR1 $(cat /var/run/nginx.pid 2>/dev/null) 2>/dev/null || :
endscript
}
@EOF
精彩评论