如何在bash的here-document中输入制表符?

35
这里文档的定义在这里:http://en.wikipedia.org/wiki/Here_document 如何在这里文档中输入制表符?例如:
cat > prices.txt << EOF
coffee\t$1.50
tea\t$1.50
burger\t$5.00
EOF

更新:

此问题中涉及的问题:

  1. 扩展制表符
  2. 不扩展美元符号
  3. 嵌入here-doc到文件中,例如脚本

这个网站也很有趣:http://tldp.org/LDP/abs/html/abs-guide.html#GENERATESCRIPT - D W
12个回答

32
TAB="$(printf '\t')"

cat > prices.txt << EOF
coffee${TAB}\$1.50
tea${TAB}\$1.50
burger${TAB}\$5.00
EOF

+1 感谢您提供了一种在 here-docs 中包含制表符的简单解决方案。 - D W
5
一个特定于Bash的快捷方式是TAB=$'\t',利用了gettext的特性。 - Daenyth
@Daenyth,谢谢你的提示,这是一种更优雅的方式。 - D W
我更喜欢${t},但这也很棒。我认为当只需要标签页时,这是在这里呈现的最佳解决方案。(顺便说一句- printf是更可移植的选项) - MTeck

20

你可以将here doc嵌入到脚本中,并将其分配给变量,而无需使用单独的文件:

#!/bin/bash
read -r -d '' var<<"EOF"
coffee\t$1.50
tea\t$1.50
burger\t$5.00
EOF

然后使用printfecho -e命令将\t字符转换为制表符。您可以将其输出到文件中:

printf "%s\n" "$var" > prices.txt

或者使用printf -v将变量的值赋给自己:

printf -v var "%s\n" "$var"

现在var或文件prices.txt包含实际制表符而不是\t。 您可以在读取here doc时处理它,而不是将其存储在变量中或写入文件中。
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

请注意,在这种情况下,我使用了<<-作为here doc运算符。这使我能够缩进here doc的行以提高可读性。缩进必须仅由制表符组成(不能使用空格)。

1
+1 我从这个答案中学到了最多,它解决了我在使用 here-docs 时遇到的所有问题,并提供了一些有效使用它们的方法。 - D W
2
使用bash v4.4.19时,printf需要使用%b格式说明符来扩展转义字符(\t)的制表符。请参见https://unix.stackexchange.com/a/454069/29426。 - akhan

9
对于我来说,在bash shell中插入一个制表符,我会先按ctrl-V,然后再按ctrl-I。这样可以避免shell拦截制表符,否则它会有“特殊”的含义。也可以使用ctrl-V后跟制表符。
在here文档中嵌入美元符号时,您需要禁用shell变量的插值,否则需要在每个美元符号前加上反斜杠进行转义(即\$)。
使用您的示例文本,我最终在prices.txt中得到了这个内容。
coffee\t.50
tea\t.50
burger\t.00

因为$1$5未设置。可以通过引用终止符来关闭插值,例如:
cat > prices.txt <<"EOF"

1
谢谢你的回答。如果我想将这个 here doc 存储到文件中以便以后运行怎么办?例如,如果我想使用 here-doc 来提供一个示例文件,当运行特定脚本时并将 here-doc 作为注释存储在与脚本相同的文件中以供以后参考,该怎么做? - D W
@D W - 我不确定你在问什么 - 你肯定可以在脚本中嵌入文档。 - martin clayton
感谢您提到了如何在 here-docs 中包含美元符号而不被解释的解决方案。 - D W
在脚本中使用的here文件中,添加^V<tab>也可以工作。谢谢。很有用!这也回答了@DW的问题。(在emacs中插入字面的^V,输入^Q^V;在vi中插入字面的^V,输入^V^V,就像在shell中一样) - undefined

5

正如其他人所说,当您输入时,可以键入CTRL-V然后按Tab键插入一个制表符。

您还可以暂时禁用bash tab-completion,例如,如果您想粘贴文本或者想要输入具有大量制表符的长here-doc:

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

编辑:如果您使用iTerm 2的Mac电脑,现在有一个“带文字制表符粘贴”选项,允许将带有制表符的代码粘贴到bash命令行上。


2

我注意到正确答案已经给出,但是我尝试总结成更简洁的答案。

1. 在这个文档中,你可以使用字面上的制表符而没有任何问题。

如果你想在Bash提示符中输入一个字面上的制表符,你需要转义它。 转义字符是ctrl-V(除非你有自定义绑定来覆盖它)。

$ echo -n 't<ctrl-v><tab>ab' | hexdump -C
00000000  74 09 61 62                                       |t.ab|
00000004

在大多数程序员编辑器中,插入一个字面制表符应该很容易(尽管某些编辑器可能需要转义)。在Emacs中,ctrl-Q TAB会插入一个字面制表符。
为了更易读,最好使用某种转义而不是字面制表符。在Bash中,$'...'字符串语法很方便。
为了防止变量扩展,引用所有美元符号或将此处文档终止符放在引号中。
$ 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

在这个上下文中,无论您使用单引号还是双引号都没有关系。
3. 我不确定我是否理解了这个子问题。Here document的目的是将其嵌入到脚本中。前面的示例说明了如何在脚本或命令行中将here document传递给hexdump。
如果您想多次使用相同的here document,则没有直接实现这一点的简单方法。脚本可以将here document写入临时文件,然后将该临时文件传递给多个命令,最后删除该临时文件。(请注意,在脚本被中断的情况下,使用trap来删除临时文件。)
您也可以将here document的内容放入变量中,并进行插值处理。
# 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

你可以等价地使用echo -E "$data" | wc; echo -E "$data" | grep -c $'\t',但是使用echo不太优雅,可能不太可移植(尽管如果你的目标是bash,所有的echo应该都是相同的。如果你的目标是Bourne shell,你也可能为每个echo花费一个外部进程)。

2

2
当将选项卡粘贴到heredoc中时,它们会消失,因为bash仍将其解释为特殊字符,标记自动完成序列/请求的开始。
如果您想在当前shell中快速粘贴heredoc,则可以通过在当前shell的生命周期内禁用自动完成来实现此目的。
如果您粘贴包含制表符的heredoc,则会发生以下正常自动完成情况:
$ cat <<EOF
TABAFTERTABBEFORE
TABAFTERTABBEFORE
TABAFTERTABBEFORE
TABAFTERTABBEFORE
TABAFTERTABBEFORE
EOF
TABAFTERTABBEFORE
TABAFTERTABBEFORE
TABAFTERTABBEFORE
TABAFTERTABBEFORE
TABAFTERTABBEFORE

运行以下命令:

bind "set disable-completion on"

再次粘贴,您的选项卡将被保留:

$ cat <<EOF
> TABAFTER      TABBEFORE
> TABAFTER      TABBEFORE
> TABAFTER      TABBEFORE
> TABAFTER      TABBEFORE
> TABAFTER      TABBEFORE
> EOF
TABAFTER    TABBEFORE
TABAFTER    TABBEFORE
TABAFTER    TABBEFORE
TABAFTER    TABBEFORE
TABAFTER    TABBEFORE

如果要复制一个包含制表符的格式化块的heredoc,这将非常方便。关闭扩展bind "set disable-completion off",粘贴带有制表符的heredoc,然后再打开扩展bind "set disable-completion on" - Chris

1

关于子问题#3,我理解为:

"如果我想将here-doc注释与脚本存储在同一个文件中以供日后参考,该怎么办?"

将脚本名称用作here doc的输出,使用追加而不是替换,假设执行程序也具有写入权限。在here doc块中,Shell注释不会被解释。

_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

以类似的方式,您可以使用 here doc 来编写一个新脚本,将变量扩展到值,执行它,然后您就拥有了一个精确记录运行情况的记录,而不必追踪代码。

1
如果您想在文件缩进和heredoc中使用选项卡:您只需要使用空格将缩进的制表符与文档的制表符分开即可。
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

唯一的缺点是,没有缩进的行看起来有点奇怪;脚本前有一个前导空格字符。
  • /var/log/nginx/*log
  • }
然而,在最终文件中不会存在这种情况(使用sed 's/^ //g'而不是cat)。

0
一个简单而直接的解决原问题的方法是使用 $(echo $'...') 惯用语:
cat > prices.txt << EOF
$(echo $'coffee\t$1.50')
$(echo $'tea\t$1.50')
$(echo $'burger\t$5.00')
EOF

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接