如何在Bash中将字符串分割成多行?

205

如何将我的长字符串常量分成多行?

我知道你可以这样做:

echo "continuation \
lines"
>continuation lines

但是,如果你有缩进的代码,它就不太好用了:

    echo "continuation \
    lines"
>continuation     lines

8
Google的Bash Shell风格指南建议在“如果必须编写超过80个字符的字符串”的情况下使用“here”文档。请参见@tripleee的答案。 - Trevor Boyd Smith
1
https://google.github.io/styleguide/shellguide.html#line-length-and-long-strings -- 这是新文档中的直接链接 - jcollum
1
答案清楚地展示了Bash语言是多么有趣 :-) - matanster
12个回答

205

这可能是你想要的

$       echo "continuation"\
>       "lines"
continuation lines
如果这会创建两个传递给echo的参数,而你只想要一个参数,那么我们可以考虑使用字符串拼接。在bash中,把两个字符串放在一起即可进行拼接:
$ echo "continuation""lines"
continuationlines

没有缩进的续行是一种将字符串拆分的方法:

$ echo "continuation"\
> "lines"
continuationlines
但是当使用缩进时:
$       echo "continuation"\
>       "lines"
continuation lines

你得到了两个参数,因为这不再是连接操作。

如果你想要一个跨越多行的单独字符串,同时进行缩进但不想获得所有的空格,可以尝试放弃续行符并使用变量的方法:

$ a="continuation"
$ b="lines"
$ echo $a$b
continuationlines

这将允许您拥有清晰缩进的代码,但会增加额外的变量。如果将变量设为局部的话,应该不会有太多问题。


1
谢谢您的帮助,但是虽然这样可以去掉空格,但它们现在成为了单独的参数(Bash将第二行的空格解释为参数分隔符),并且现在只有通过echo命令才能正确打印。 - user880248
1
哦,你想让一个(bash)字符串跨越多行!我现在明白了。 - Ray Toal
7
一个变量的解决方案: s="字符串1" s+="字符串2" s+=" 字符串3" echo "$s" - Johnny Thunderman

40

使用<<-HERE终止符的Here文档可以很好地处理缩进的多行文本字符串。它将删除here文档中的所有前导制表符(但行终止符仍将保留)。

cat <<-____HERE
    continuation
    lines
____HERE

参见http://ss64.com/bash/syntax-here.html

如果您需要保留一些但不是全部的前导空格,可以使用类似以下的方法:

sed 's/^  //' <<____HERE
    This has four leading spaces.
    Two of them will be removed by sed.
____HERE

或者使用tr命令来去掉换行符:

tr -d '\012' <<-____
    continuation
     lines
____

第二行开头有一个制表符和一个空格;在heredoc终止符之前,制表符将被破折号运算符删除,而空格将被保留。

对于需要跨越多行的长复杂字符串,我喜欢使用printf

printf '%s' \
    "This will all be printed on a " \
    "single line (because the format string " \
    "doesn't specify any newline)"

它还可以在需要嵌入非平凡的 shell 脚本片段到另一种语言中的情况下发挥作用,在这种情况下,主机语言的语法不允许使用 Here 文档,例如在 MakefileDockerfile 中。

printf '%s\n' >./myscript \
    '#!/bin/sh` \
    "echo \"G'day, World\"" \
    'date +%F\ %T' && \
chmod a+x ./myscript && \
./myscript

1
对我不起作用。Ubuntu 16.04。我得到了两行,而不是预期的连接成一行的结果。 - Penghe Geng
1
@PengheGeng,确实,这解决了消除缩进的问题,但并没有解决将两行连接在一起的问题。您仍然可以在行末使用反斜杠加换行符来连接两行。 - tripleee
printf("Hello World!"); 翻译:printf("你好,世界!"); - tripleee
对于将来看到这个的任何人,关于在特定字符数处拆分多行字符串的相关问题,可以参考以下链接的另一个答案:https://dev59.com/M6nka4cB1Zd3GeqPTcUW#49261098。它还推荐使用文档语法,并可能加强其在bash中的使用以满足此需求。我建议分别测试每个命令(例如echo、cat、printf)的使用效果。 - Pablo Adames

17
你可以使用 bash 数组
$ str_array=("continuation"
             "lines")

然后

$ echo "${str_array[*]}"
continuation lines

由于bash手册上的解释,所以会有额外的空格:

如果单词被双引号括起来,${name[*]}将扩展为一个单词,其中每个数组成员的值由IFS变量的第一个字符分隔

因此,设置IFS=''以消除额外的空格。

$ IFS=''
$ echo "${str_array[*]}"
continuationlines

1
你可能应该使用 "${str_array[@]}" - tripleee
1
很好的发现!谢谢。对于 echo 来说没有关系,但对于其他情况可能会有很大的区别。https://dev59.com/M3A75IYBdhLWcg3wVniI - tworec

11

在某些情况下,利用Bash的连接能力可能是合适的。

示例:

temp='this string is very long '
temp+='so I will separate it onto multiple lines'
echo $temp
this string is very long so I will separate it onto multiple lines

来自Bash Man页面的PARAMETERS部分:

name=[value]...

当赋值语句将值分配给shell变量或数组索引时,在此上下文中,可以使用+=运算符来附加或添加到变量的先前值。当应用+=到已设置整数属性的变量时,value被评估为算术表达式,并添加到变量的当前值中,该值也被评估。当使用复合赋值(见下面的Arrays)应用于数组变量时,变量的值不会取消设置(如使用=时),并且新值将从一个最大索引比数组大1的位置(对于索引数组)开始附加,或作为关联数组中的额外键值对添加。当应用于字符串值变量时,value将被扩展并附加到变量的值。


这可能是本页面上最好的建议。使用HEREDOC并将其管道传输到<CR>的翻译中是非常不直观的,并且在字符串实际需要具有离散放置的行分隔符时会失败。 - ingyhere

2
您可以按照缩进所需的方式,仅使用换行符(而不使用反斜杠)将其分隔开,并剥离新行。
例如:
echo "continuation
of 
lines" | tr '\n' ' '

或者,如果它是一个变量定义,换行符会自动转换为空格。因此,仅在适用时去掉额外的空格。

x="continuation
of multiple
lines"
y="red|blue|
green|yellow"

echo $x # This will do as the converted space actually is meaningful
echo $y | tr -d ' ' # Stripping of space may be preferable in this case

2

我遇到了这样一种情况,需要将长消息作为命令参数发送,并且必须遵守行长度限制。命令看起来像这样:

somecommand --message="I am a long message" args

我解决这个问题的方法是将消息作为文档(如@tripleee建议的那样)移出。但是,文档成为了标准输入,所以需要读回来。我采用了以下方法:
message=$(
    tr "\n" " " <<-END
        This is a
        long message
END
)
somecommand --message="$message" args

这样做的好处是$message可以像字符串常量一样使用,不会有额外的空格或换行符。

请注意,上面的实际消息行都以tab字符为前缀,这是由于此处文档本身(因为使用了<<-)而被剥离掉。仍然存在换行符,在使用tr替换为空格时会被替换。

还要注意,如果您不删除换行符,则它们将在展开"$message"时按原样显示。在某些情况下,您可以通过删除$message周围的双引号来解决问题,但是该消息将不再是单个参数。


2

通过巧妙的语法使用,也可以实现行连接。

echo的情况下:

# echo '-n' flag prevents trailing <CR> 
echo -n "This is my one-line statement" ;
echo -n " that I would like to make."
This is my one-line statement that I would like to make.

在vars的情况下:
outp="This is my one-line statement" ; 
outp+=" that I would like to make." ; 
echo -n "${outp}"
This is my one-line statement that I would like to make.

在处理变量时,另一种方法是:

outp="This is my one-line statement" ; 
outp="${outp} that I would like to make." ; 
echo -n "${outp}"
This is my one-line statement that I would like to make.

看,完成了!


"echo -n" 是可疑的; 你应该使用 "printf" 来代替。 - tripleee
@tripleee,使用GNU coreutil有什么可疑的地方吗?在这种简单情况下,它正好做到了预期的效果,甚至允许终端正确换行输出。即使是Google Shell Style Guide,在简单情况下也会类似地使用它(除了HEREDOC的建议)。这不是特殊格式的解决方案。 - ingyhere
“echo -n” 的问题在于它不能可靠地按照你所描述的方式工作。许多非 GNU 平台都有不同的 “echo” 实现,具有不同的功能。只要你使用 Bash 内置的命令,这只是一个小问题;但为什么要使你的解决方案变得不太可移植,而实际上更优雅、更可靠的解决方案却是可移植的呢? - tripleee
我在 Google 风格指南中找不到任何关于 echo -n 的示例。 - tripleee
@tripleee 一个问题是printf本身有相当大的可移植性问题。在2021年,抱怨echo中删除尾随<CR>的-n标志是一件非常微不足道的事情。我甚至会认为它提供了更多的控制,而不是吐出任何行结束符。个人而言,我更喜欢使用echo -ne "${outp}",它可以强制解释转义字符。建议严格遵循几十年的兼容性,使用带有HEREDOC的cat。但实际上,原始发布者正在询问如何使用echo,并且该问题已标记为Bash - ingyhere

2

这并不完全是用户要求的,但是另一种创建跨越多行的长字符串的方法是逐步构建它,像这样:

$ greeting="Hello"
$ greeting="$greeting, World"
$ echo $greeting
Hello, World

显然,在这种情况下,一次构建它可能更加简单,但是当处理较长的字符串时,采用这种风格可以使其更轻量级和易于理解。

1

在 @tripleee 的 printf 示例基础上(+1):

LONG_STRING=$( printf '%s' \
    'This is the string that never ends.' \
    ' Yes, it goes on and on, my friends.' \
    ' My brother started typing it not knowing what it was;' \
    " and he'll continue typing it forever just because..." \
    ' (REPEAT)' )

echo $LONG_STRING

This is the string that never ends. Yes, it goes on and on, my friends. My brother started typing it not knowing what it was; and he'll continue typing it forever just because... (REPEAT)

我们已经在句子之间加入了明确的空格,例如 "' Yes..."。另外,如果不需要这个变量:

echo "$( printf '%s' \
    'This is the string that never ends.' \
    ' Yes, it goes on and on, my friends.' \
    ' My brother started typing it not knowing what it was;' \
    " and he'll continue typing it forever just because..." \
    ' (REPEAT)' )"

This is the string that never ends. Yes, it goes on and on, my friends. My brother started typing it not knowing what it was; and he'll continue typing it forever just because... (REPEAT)

永不停歇的歌曲致谢

(注:该链接为英文维基百科,需要自行翻译)

1

根据您愿意接受的风险以及了解和信任数据的程度,您可以使用简单的变量插值。

$: x="
    this
    is
       variably indented
    stuff
   "
$: echo "$x" # preserves the newlines and spacing

    this
    is
       variably indented
    stuff

$: echo $x # no quotes, stacks it "neatly" with minimal spacing
this is variably indented stuff

那么,只需执行 MY_VAR = $(echo x $),然后你就得到了它。在我看来,这是保持我的代码可读性最好、最简单的答案,每行都是一个变量,保存着一个字符串本身。我需要导出这个值,但你不能导出数组。 - DKebler
@DKebler 你的评论包含多个语法错误。很难猜测你的意思,或者认真考虑周围的建议。另外请参考无用的echo用法 - tripleee

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