带额外空格的多行字符串(保留缩进)

我想写一些预定义的文本与以下文件:

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


echo -e $text > filename

我期待的是这样的:

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

但我明白了:

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

我确信在每个\n之后没有空间,但是额外的空间是如何出来的?

741401 次浏览

在bash脚本中,以下工作:

#!/bin/sh


text="this is line one\nthis is line two\nthis is line three"
echo -e $text > filename

另外:

text="this is line one
this is line two
this is line three"
echo "$text" > filename

Cat文件名给出:

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

echo在传递给它的参数之间添加空格。$text受到变量扩展和单词分割的影响,所以你的echo命令相当于:

echo -e "this" "is" "line" "one\n" "this" "is" "line" "two\n"  ...

你可以看到在“this”之前加了一个空格。你可以删除换行符,并引用$text来保留换行符:

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


echo "$text" > filename

或者你可以使用printf,它比echo更健壮和可移植:

printf "%s\n" "this is line one" "this is line two" "this is line three" > filename

在支持大括号展开的bash中,你甚至可以这样做:

printf "%s\n" "this is line "{one,two,three} > filename

对于这个目的,Heredoc听起来更方便。它用于向前女友等命令解释器程序发送多个命令

cat << EndOfMessage
This is line 1.
This is line 2.
Line 3.
EndOfMessage

<<后面的字符串表示停止的位置。

要将这些行发送到文件,请使用:

cat > $FILE <<- EOM
Line 1.
Line 2.
EOM

你也可以将这些行存储到一个变量中:

read -r -d '' VAR << EOM
This is line 1.
This is line 2.
Line 3.
EOM

这将行存储到名为VAR的变量中。

打印时,请记住变量周围的引号,否则将看不到换行符。

echo "$VAR"

更好的是,您可以使用缩进使它在代码中更加突出。这次只需要在<<后面添加-来阻止标签出现。

read -r -d '' VAR <<- EOM
This is line 1.
This is line 2.
Line 3.
EOM

但是在代码中必须使用制表符而不是空格来缩进。

我发现了更多的解决方案,因为我想有每一行适当缩进:

  1. 你可以使用echo:

    echo    "this is line one"   \
    "\n""this is line two"   \
    "\n""this is line three" \
    > filename
    

    如果你把"\n"放在一行末尾的\之前,它就不起作用了

  2. 或者,你可以使用printf来获得更好的可移植性(我碰巧在echo上有很多问题):

    printf '%s\n' \
    "this is line one"   \
    "this is line two"   \
    "this is line three" \
    > filename
    
  3. Yet another solution might be:

    text=''
    text="${text}this is line one\n"
    text="${text}this is line two\n"
    text="${text}this is line three\n"
    printf "%b" "$text" > filename
    

    text=''
    text+="this is line one\n"
    text+="this is line two\n"
    text+="this is line three\n"
    printf "%b" "$text" > filename
    
  4. Another solution is achieved by mixing printf and sed.

    if something
    then
    printf '%s' '
    this is line one
    this is line two
    this is line three
    ' | sed '1d;$d;s/^    //g'
    fi
    

    重构这样格式化的代码并不容易,因为您将缩进级别硬编码到代码中

  5. 可以使用辅助函数和一些变量替换技巧:

    unset text
    _() { text="${text}${text+
    }${*}"; }
    # That's an empty line which demonstrates the reasoning behind
    # the usage of "+" instead of ":+" in the variable substitution
    # above.
    _ ""
    _ "this is line one"
    _ "this is line two"
    _ "this is line three"
    unset -f _
    printf '%s' "$text"
    

如果你试图将字符串转换为变量,另一种简单的方法是这样的:

USAGE=$(cat <<-END
This is line one.
This is line two.
This is line three.
END
)

如果你用制表符缩进字符串(例如,'\t'),缩进将被删除。如果使用空格缩进,则缩进将保留。

注意:最后一个右括号在另一行,很重要。END文本必须单独出现在一行中。

下面是我喜欢的方式来分配一个多行字符串的变量(我认为它看起来不错)。

read -r -d '' my_variable << \
_______________________________________________________________________________


String1
String2
String3
...
StringN
_______________________________________________________________________________

在这两种情况下,下划线的数量是相同的(这里是80)。

如果你把它放在下面,它就会工作:

AA='first line
\nsecond line
\nthird line'
echo $AA
output:
first line
second line
third line

这里只提一个简单的单行连接,因为它有时很有用。

# for bash


v=" guga "$'\n'"   puga "


# Just for an example.
v2="bar "$'\n'"   foo "$'\n'"$v"


# Let's simplify the previous version of $v2.
n=$'\n'
v3="bar ${n}   foo ${n}$v"


echo "$v3"

你会得到这样的结果

bar
foo
guga
puga

所有前导和结尾空格将被保留

echo "$v3" > filename

有很多方法可以做到这一点。对我来说,将缩进的字符串输送到sed中工作得很好。

printf_strip_indent() {
printf "%s" "$1" | sed "s/^\s*//g"
}


printf_strip_indent "this is line one
this is line two
this is line three" > "file.txt"

这个答案是基于Mateusz Piotrowski的回答,但做了一些改进。

我听到这个答案,但也想把它输送到另一个命令。给出的答案是正确的,但如果有人想要管道它,您需要在像这样的多行字符串之前管道它

echo | tee /tmp/pipetest << EndOfMessage
This is line 1.
This is line 2.
Line 3.
EndOfMessage

这将允许您拥有多行字符串,但也可以将其放在后续命令的stdin中。

或者用空格保持文本缩进:

#!/bin/sh


sed 's/^[[:blank:]]*//' >filename <<EOF
this is line one
this is line two
this is line three
EOF

相同但使用变量:

#!/bin/sh


text="$(sed 's/^[[:blank:]]*//' << whatever
this is line one
this is line two
this is line three
)"


echo "$text" > filename

: -)

难读:

看起来太像了:)(很难读懂)对我的一般口味来说:

cat << EndOfMessage
This is line 1.
This is line 2.
Line 3.
EndOfMessage

更好,更容易阅读:

让我们找一些更python化的东西(这仍然是bash):

text="this is line one
this is line two
this is line three\n"
dedent text
printf "$text"             # print to screen
printf "$text" > file.txt  # print to a file

啊…这是更好的。:)这让我想起了Python的textwrap.dedent()函数我用在这里

下面是神奇的dedent函数的样子:

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

示例输出到屏幕:

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

如果不先调用dedent text ',输出将如下所示:

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

变量text被传递给dedent 通过引用,这样函数被修改的内部就会影响函数的变量

要了解更多细节、解释和参考资料,请参阅我的另一个答案:相当于python在bash中的textwrap dedent

你最初的尝试有问题

OP的引用(加上我的强调):

我确定每个\n后面没有空格,但是额外的空间是如何产生的?后面没有空格

你最初的想法是这样的:

text="this is line one\n
this is line two\n
this is line three"
echo -e $text

...但是你的输出在第2行和第3行之前有一个额外的空间。为什么?

通过推理和实验,我的结论是echo转换行末的实际换行符(当您实际按输入时得到的换行符)进入一个空间。因此,该空格在文本中的\n行之前显示。

因此,解决方案是在每一行的末尾转义真正的换行符,在字符串的引号内的任何一行的末尾放一个反斜杠\,就像这样:

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


echo -e "$text"

不要在后面加上一个空格之前和反斜杠(像这样:text="this is line one\n \),否则这个空格会直接回到你的输出中,导致你在额外的空格上遇到同样的问题!

或者,使用上面的dedent函数使用我的技术,它还具有能够与代码一起缩进的附加功能,使其看起来非常漂亮、漂亮且可读。