开发者

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:

  1. Expanding the tab character
  2. While not expanding the dollar sign
  3. 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
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜