如何在Bash中输出多行字符串?

424

如何在Bash中输出多行字符串而不使用多个echo调用,类似于以下方式:

echo "usage: up [--level <n>| -n <levels>][--help][--version]"
echo 
echo "Report bugs to: "
echo "up home page: "

我正在寻找一种便携的方式,只使用Bash内置工具实现。


6
如果您在回应错误的调用时输出使用消息,通常应该将该消息发送到标准错误流而不是标准输出流,可以使用echo >&2 ... - Mark Reed
3
使用信息可以通过输入 --help 命令输出(应该输出到标准输出)。 - helpermethod
3
对于其他人,有关“here documents”的更多信息可在以下网址找到:http://www.tldp.org/LDP/abs/html/here-docs.html - Jeffrey Martinez
1
请查看Gordon Davidson的基于printf的解决方案。尽管在echocat的基础上处于阴影之中,但它似乎要少得多。不可否认,printf语法代表了一定的学习曲线,但我想听听其他缺点(兼容性、性能等...)。 - mjv
1
相关:https://dev59.com/MWAg5IYBdhLWcg3wI4LD - Anton Tarasenko
12个回答

486

这种情况下,常常使用Here文档。

cat << EOF
usage: up [--level <n>| -n <levels>][--help][--version]

Report bugs to: 
up home page:
EOF

它们在所有 Bourne 派生的 shell 中都受到支持,包括所有版本的 Bash。


8
可以 - 但是 cat 不是内置命令。 - Mark Reed
14
@MarkReed说的是真的,但它通常是一直可用的(除非在特殊情况下可能不可用)。 - Dennis Williamson
10
+1 谢谢。我最终使用 read -d '' help <<- EOF ... 将多行字符串读入变量,然后输出了结果。 - helpermethod
4
我能把HEREDOC保存到一个变量里吗? - chovy
3
真是个天才,谢谢@DennisWilliamson!专业提示:如果您需要从反引号等内容中转义内容,则可以在第一行上双引号定界符,如cat << "EOF" - Joshua Pinter
@chovy:请看我在这里的回答(https://dev59.com/DnM_5IYBdhLWcg3w6X5e#1655389)。 - Dennis Williamson

275

或者您可以这样做:

echo "usage: up [--level <n>| -n <levels>][--help][--version]

Report bugs to: 
up home page: "

2
@OliverWeiler:它甚至可以在Bourne shell(如Dash和Heirloom Bourne Shell)中运行。 - Dennis Williamson
18
如果您需要将此内容用于函数中,那么情况并不太好,因为您要么需要将字符串完全缩进到文件的左侧,要么保持缩进以与代码的其余部分对齐,但这样它也将打印缩进。 - s g
4
工作正常。请注意,如果您不希望字符串被解释,请使用 '(单引号)而不是 "(双引号)。如果您想在输出中使用 $something 或 something 而不是被替换,请注意。 - Eric

104

受到本页面上深思熟虑的答案的启发,我创建了一种混合方法,我认为这是最简单和最灵活的方法。你觉得呢?

首先,我将使用定义在一个变量中,这样可以在不同的上下文中重复使用。这种格式非常简单,几乎是所见即所得的,不需要添加任何控制字符。在我的看来,这似乎相当可移植(我在MacOS和Ubuntu上运行它)

__usage="
Usage: $(basename $0) [OPTIONS]

Options:
  -l, --level <n>              Something something something level
  -n, --nnnnn <levels>         Something something something n
  -h, --help                   Something something something help
  -v, --version                Something something something version
"

那么我可以简单地使用它作为

echo "$__usage"

甚至更好的是,在解析参数时,我只需在一行中回显它:

levelN=${2:?"--level: n is required!""${__usage}"}

4
这个方法对我在一个脚本中起作用,而上面的答案则不行(无需修改)。 - David Welch
6
这种方法比使用像\t和\n这样的字符要干净得多,因为这些字符在文本中很难找到,并且会使输出与脚本中的字符串非常不同。 - s g
2
由于某些原因,它在我的电脑上将所有内容都打印在同一行:/ - Nicolas de Fontenay
2
如果您使用@jorge的解决方案并发现所有内容都在同一行上,请确保将变量用引号括起来:echo $__usage将会在一行上打印所有内容,而echo "$__usage"将保留换行符。 - user1165471
6
@Nicolas说:在echo "$__usage"中使用双引号对我很有必要,而echo $__usage则不起作用。 - Mario
1
这实际上是我用过的一个解决方案。Printf 做了很多事情,对于我的多行 XML,它可能需要大量转义,因为它会完全破坏内容。我将 foo 赋值为 cat <<EOF .... EOF && echo "$foo" - Jilles van Gurp

58

使用-e选项,然后您可以在字符串中使用\n打印换行符。

例如:

echo -e "This will be the first line \nand this will be on the second line"

7
这些 man 页面是关于系统提供的“echo”命令,“/bin/echo”,在 Mac OS 上没有“-e”选项。当您在这些系统上使用 bash 时,其内置的“echo”命令将接管。您可以通过明确输入“/bin/echo whatever”并观察行为差异来查看这一点。要查看内置命令的文档,请输入“help echo”。 - Mark Reed
1
/bin/echo 在不同的操作系统中通常是不同的,也与 Bash 内置的 echo 不同。 - Dennis Williamson
@MarkReed:我稍后会尝试,但感谢您提供的信息。+1。我会在这里留下我的答案,因为有很多好的讨论正在进行。 - nhahtdh
9
“echo -e”不具备可移植性——例如,某些echo实现将“-e”作为输出的一部分打印出来。如果您想要可移植性,请改用printf。例如,在OS X 10.7.4上,/bin/echo就是这样的。我记得在10.5.0下,bash内置的echo也很奇怪,但我不记得细节了。 - Gordon Davisson
2
以前我被 echo -e 坑过...一定要使用 printf 或者带有 heredoc 的 cat<<- 变体的 here docs 特别好用,因为你可以在输出中去掉前导缩进,但在脚本中保留缩进以提高可读性。 - zbeekman

27

既然我在评论中推荐了printf,那么我应该给出一些使用示例(虽然对于打印使用信息,我更可能使用Dennis或Chris的答案)。 printf的使用要比echo复杂一些。它的第一个参数是格式字符串,在其中转义(如\n)总是会被解释;它还可以包含以%开头的格式指令,用于控制任何附加参数的包含方式和位置。以下是两种不同的方法来使用它来打印使用信息:

首先,您可以将整个消息包含在格式字符串中:

printf "usage: up [--level <n>| -n <levels>][--help][--version]\n\nReport bugs to: \nup home page: \n"

请注意,与echo不同的是,您必须显式包含最后的换行符。此外,如果消息恰好包含任何%字符,则必须将其写为%%。如果您想要包括bug报告和主页地址,它们可以相当自然地添加:

printf "usage: up [--level <n>| -n <levels>][--help][--version]\n\nReport bugs to: %s\nup home page: %s\n" "$bugreport" "$homepage"

其次,您可以使用格式字符串使其将每个额外参数打印在单独的行上:

printf "%s\n" "usage: up [--level <n>| -n <levels>][--help][--version]" "" "Report bugs to: " "up home page: "

通过这个选项,添加bug报告和主页地址是相当明显的:

printf "%s\n" "usage: up [--level <n>| -n <levels>][--help][--version]" "" "Report bugs to: $bugreport" "up home page: $homepage"

24

使用带缩进的源代码时,您可以使用<<-(后跟破折号)来忽略前导制表符(但不包括前导空格)。

例如:

if [ some test ]; then
    cat <<- xx
        line1
        line2
xx
fi

缩进文本并删除前导空格:

line1
line2

那对我没用。你在用什么shell? - four43
在Ubuntu的bash 4.4.19中无法工作。它没有删除line1和line2之前的空格。 - four43
1
@four43,你是对的。无法删除前导空格。但是,可以删除前导制表符。因此,我将我的答案从制表符和空格更正为制表符而非空格。对于我的错误表示抱歉。我查看了手册,它明确指出只有制表符被删除。感谢你提醒我注意这一点。 - Elliptical view
它可以在MacOS 11.4上与bash和zsh一起使用。非常好的提示! - Philipp
专业提示:在分隔符周围使用双引号来转义一些东西,比如“xx”。 - Joshua Pinter

20

我通常使用内置的“read”命令,因为我认为它更加灵活和直观。它可以将一行内容读入变量,并允许与特殊的shell变量IFS相关联的单词分割。有关更多详细信息,请参阅此博客甚至手册。

read -r -d '' usage <<-EOF
    usage: up [--level <n>| -n <levels>][--help][--version] 

    Report bugs to: $report server
    up home page: $HOME
EOF
echo "$usage"

2
最好的一点是,<<-(带有减号)会忽略前导制表符,因此您可以在代码中缩进消息而不必在打印时进行缩进。 - BUFU

7

还有一件事,使用预定义变量(这里是msg)作为模板来使用printf

msg="First line %s
Second line %s
Third line %s
"

one='additional message for the first line'
two='2'
tri='this is the last one'

printf "$msg" "$one" "$two" "$tri"

这个 ^^^ 会将整个消息打印出来,而不是按照提供的顺序插入 %s 变量。


这在Makefiles中运行良好。 - John Tang Boyland

2
    You can write your
    text
        freely,
                   in a separate: 
                             ----file.

然后

echo "$(</pathto/your_multiline_file)"

1
做这个:
dedent() {
    local -n reference="$1"
    reference="$(echo "$reference" | sed 's/^[[:space:]]*//')"
}

text="this is line one
      this is line two
      this is line three\n"

# `text` is passed by reference and gets dedented
dedent text

printf "$text"

在不先调用dedent的情况下输出:

this is line one
      this is line two
      this is line three

...并在首先调用dedent的情况下:

this is line one
this is line two
this is line three

完整的说明,请看我之前写过的内容:

  1. 在bash中等同于Python's textwrap dedent
  2. 带额外空格(保留缩进)的多行字符串

当然,感谢@Andreas Louv在此向我展示了该函数的sed部分


1
注意:不保留缩进。另请参阅 https://stackoverflow.com/a/63228433/724752 - David Winiecki
@DavidWiniecki,是的,我应该有一天修复这个问题,通过编写一个更复杂和程序化的dedent函数版本。然后,我可以将其制作成一个bash库,包含单元测试,并在需要的地方导入(源代码),就像我在这里解释的关于bash库的用法:详细示例:如何在Bash中编写、导入、使用和测试库?。那将会很酷。 - Gabriel Staples

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