【问题标题】:How can I have a newline in a string in sh?如何在 sh 中的字符串中有换行符?
【发布时间】:2011-03-01 15:53:39
【问题描述】:

这个

STR="Hello\nWorld"
echo $STR

作为输出产生

Hello\nWorld

而不是

Hello
World

我应该怎么做才能在字符串中有换行符?

注意:这个问题与echo无关。 我知道echo -e,但我正在寻找一种解决方案,它允许将字符串(包括换行符)作为参数传递给没有类似选项来解释的 other 命令\n 是换行符。

【问题讨论】:

    标签: sh


    【解决方案1】:

    如果你使用的是 Bash,解决方法是使用$'string',例如:

    $ STR=$'Hello\nWorld'
    $ echo "$STR" # quotes are required here!
    Hello
    World
    

    如果您使用几乎任何其他 shell,只需在字符串中按原样插入换行符:

    $ STR='Hello
    > World'
    

    Bash 很不错。它不仅接受$'' 字符串中的\n。以下是 Bash 手册页的摘录:

       Words of the form $'string' are treated specially.  The word expands to
       string, with backslash-escaped characters replaced as specified by  the
       ANSI  C  standard.  Backslash escape sequences, if present, are decoded
       as follows:
              \a     alert (bell)
              \b     backspace
              \e
              \E     an escape character
              \f     form feed
              \n     new line
              \r     carriage return
              \t     horizontal tab
              \v     vertical tab
              \\     backslash
              \'     single quote
              \"     double quote
              \nnn   the eight-bit character whose value is  the  octal  value
                     nnn (one to three digits)
              \xHH   the  eight-bit  character  whose value is the hexadecimal
                     value HH (one or two hex digits)
              \cx    a control-x character
    
       The expanded result is single-quoted, as if the  dollar  sign  had  not
       been present.
    
       A double-quoted string preceded by a dollar sign ($"string") will cause
       the string to be translated according to the current  locale.   If  the
       current  locale  is  C  or  POSIX,  the dollar sign is ignored.  If the
       string is translated and replaced, the replacement is double-quoted.
    

    【讨论】:

    • 这是有效的 bash,但不是 POSIX sh。
    • +1-只是一个注释:export varDynamic="$varAnother1$varAnother2"$'\n'
    • 这确实适用于 OP 要求的文字字符串,但一旦要替换变量就会失败:STR=$"Hello\nWorld" 只是打印Hello\nWorld
    • @ssc 单引号,不是双引号
    • @ssc 您的示例中没有变量。此外,此方法需要单引号。最后,要包含插值变量,您必须将双引号和特殊单引号字符串连接在一起。例如H="Hello"; W="World"; STR="${H}"$'\n'"${W}"; echo "${STR}" 将在不同的行中回显“Hello”和“World”
    【解决方案2】:

    Echo 已经九十年代了,充满了危险,以至于它的使用应该会导致不少于 4GB 的核心转储。说真的,echo 的问题是 Unix 标准化进程最终发明了printf 实用程序,消除了所有问题的原因。

    所以要在字符串中获取换行符,有两种方法:

    # 1) Literal newline in an assignment.
    FOO="hello
    world"
    # 2) Command substitution.
    BAR=$(printf "hello\nworld\n") # Alternative; note: final newline is deleted
    printf '<%s>\n' "$FOO"
    printf '<%s>\n' "$BAR"
    

    那里!没有 SYSV 与 BSD 的回声疯狂,一切都得到了整齐的打印和对 C 转义序列的完全可移植的支持。请大家立即使用printf 来满足您的所有输出需求,不要回头。

    【讨论】:

    • 感谢您的回答,我认为它提供了非常好的建议。但是请注意,我最初的问题并不是关于如何让 echo 打印换行符,而是如何将换行符放入 shell 变量中。您还展示了如何使用 printf 来做到这一点,但我认为 amphetamachine 使用 $'string' 的解决方案更加简洁。
    • 仅适用于“干净”的定义不足。截至 2013 年,$'foo' 语法不是有效的 POSIX 语法。许多 shell 会抱怨。
    • BAR=$(printf "hello\nworld\n") 不为我打印尾随 \n
    • @Jonny 不应该;命令替换的 shell 规范说,输出末尾的一个或多个换行符被删除。我意识到这对于我的示例来说是出乎意料的,并对其进行了编辑以在 printf 中包含换行符。
    • @ismael 否,$() 被 POSIX 指定为在替换结束时删除一个或多个 字符的序列
    【解决方案3】:

    我根据其他答案所做的是

    NEWLINE=$'\n'
    my_var="__between eggs and bacon__"
    echo "spam${NEWLINE}eggs${my_var}bacon${NEWLINE}knight"
    
    # which outputs:
    spam
    eggs__between eggs and bacon__bacon
    knight
    

    【讨论】:

      【解决方案4】:

      我发现-e 标志优雅而直接

      bash$ STR="Hello\nWorld"
      
      bash$ echo -e $STR
      Hello
      World
      

      如果字符串是另一个命令的输出,我只使用引号

      indexes_diff=$(git diff index.yaml)
      echo "$indexes_diff"
      

      【讨论】:

      • 这已经在其他答案中提到过。此外,虽然它已经存在,但我刚刚编辑了这个问题,以强调这个问题不是关于echo
      • 这不是问题的答案
      • 这完全回答了我一直在寻找的问题!谢谢!
      • 在这里工作得很好。我曾经根据这个 echo 创建一个文件,它在输出中正确生成了新行。
      • echo -e 因其可移植性问题而臭名昭著。与 POSIX 之前相比,现在这不是狂野西部的情况,但仍然没有理由更喜欢 echo -e。另见stackoverflow.com/questions/11530203/…
      【解决方案5】:

      问题不在于外壳。问题实际上在于echo 命令本身,以及变量插值周围缺少双引号。您可以尝试使用echo -e,但并非所有平台都支持,而现在建议使用printf 的原因之一是为了便于携带。

      您也可以尝试将换行符直接插入到您的 shell 脚本中(如果您正在编写脚本),这样它看起来像...

      #!/bin/sh
      echo "Hello
      World"
      #EOF
      

      或等效

      #!/bin/sh
      string="Hello
      World"
      echo "$string"  # note double quotes!
      

      【讨论】:

        【解决方案6】:
        1. 唯一简单的替代方法是在变量中实际键入一个新行:

          $ STR='new
          line'
          $ printf '%s' "$STR"
          new
          line
          

          是的,这意味着在代码中需要的地方写 Enter

        2. new line 字符有几个等价物。

          \n           ### A common way to represent a new line character.
          \012         ### Octal value of a new line character.
          \x0A         ### Hexadecimal value of a new line character.
          

          但所有这些都需要某些工具 (POSIX printf) 的“解释”:

          echo -e "new\nline"           ### on POSIX echo, `-e` is not required.
          printf 'new\nline'            ### Understood by POSIX printf.
          printf 'new\012line'          ### Valid in POSIX printf.
          printf 'new\x0Aline'       
          printf '%b' 'new\0012line'    ### Valid in POSIX printf.
          

          因此,需要该工具来构建带有换行符的字符串:

          $ STR="$(printf 'new\nline')"
          $ printf '%s' "$STR"
          new
          line
          
        3. 在某些 shell 中,序列 $' 是一种特殊的 shell 扩展。 已知在 ksh93、bash 和 zsh 中工作:

          $ STR=$'new\nline'
          
        4. 当然,更复杂的解决方案也是可能的:

          $ echo '6e65770a6c696e650a' | xxd -p -r
          new
          line
          

          或者

          $ echo "new line" | sed 's/ \+/\n/g'
          new
          line
          

        【讨论】:

          【解决方案7】:

          单引号 '...\n...' 前的 $ 如下,但双引号不起作用。

          $ echo $'Hello\nWorld'
          Hello
          World
          $ echo $"Hello\nWorld"
          Hello\nWorld
          

          【讨论】:

            【解决方案8】:

            我不是 bash 专家,但这个对我有用:

            STR1="Hello"
            STR2="World"
            NEWSTR=$(cat << EOF
            $STR1
            
            $STR2
            EOF
            )
            echo "$NEWSTR"
            

            我发现这更容易格式化文本。

            【讨论】:

            • 好老heredoc。而且它可能也符合 POSIX 标准!
            • echo "$NEWSTR"$NEWSTR 周围的引号在这里很重要
            【解决方案9】:

            免责声明:我首先写了这个,然后偶然发现了这个问题。我认为此解决方案尚未发布,并看到 tlwhitec 确实发布了类似的答案。我仍然发布这个是因为我希望它是一个有用和彻底的解释。

            简答:

            这似乎是一个非常便携的解决方案,因为它适用于相当多的外壳(见评论)。
            这样您就可以在变量中添加一个真正的换行符。

            此解决方案的好处是您不必在源代码中使用换行符,因此您可以缩进 您的代码以任何您想要的方式,并且该解决方案仍然有效。这使它变得健壮。它也是便携式的。

            # Robust way to put a real newline in a variable (bash, dash, ksh, zsh; indentation-resistant).
            nl="$(printf '\nq')"
            nl=${nl%q}
            

            更长的答案:

            上述方案的解释:

            换行符通常会由于命令替换而丢失,但为了防止这种情况,我们添加了一个 'q' 并在之后将其删除。 (下面会进一步解释双引号的原因。)

            我们可以证明该变量包含一个实际的换行符(0x0A):

            printf '%s' "$nl" | hexdump -C
            00000000  0a  |.|
            00000001
            

            (请注意,'%s' 是必需的,否则 printf 会将文字 '\n' 字符串转换为实际的 0x0A 字符,这意味着我们什么都不会证明。)

            当然,除了这个答案中提出的解决方案,人们也可以使用它(但是......):

            nl='
            '
            

            ...但它的健壮性较差,并且很容易因意外缩进代码或事后忘记将其缩进而损坏,这使得在(缩进)函数中使用不方便,而早期的解决方案是健壮的。

            现在,至于双引号:
            nl="$(printf '\nq')" 那样在命令替换周围加上双引号" 的原因是,您甚至可以在变量赋值前加上local 关键字或内置(例如在函数中),它仍然适用于所有shell,否则dash shell 会遇到麻烦,因为 dash 否则会丢失 'q' 并且你最终会得到一个空的 'nl' 变量(同样,由于命令替换)。
            用另一个例子可以更好地说明这个问题:

            dash_trouble_example() {
                e=$(echo hello world) # Not using 'local'.
                echo "$e" # Fine. Outputs 'hello world' in all shells.
            
                local e=$(echo hello world) # But now, when using 'local' without double quotes ...:
                echo "$e" # ... oops, outputs just 'hello' in dash,
                          # ... but 'hello world' in bash and zsh.
            
                local f="$(echo hello world)" # Finally, using 'local' and surrounding with double quotes.
                echo "$f" # Solved. Outputs 'hello world' in dash, zsh, and bash.
            
                # So back to our newline example, if we want to use 'local', we need
                # double quotes to surround the command substitution:
                # (If we didn't use double quotes here, then in dash the 'nl' variable
                # would be empty.)
                local nl="$(printf '\nq')"
                nl=${nl%q}
            }
            

            上述方案的实际例子:

            # Parsing lines in a for loop by setting IFS to a real newline character:
            
            nl="$(printf '\nq')"
            nl=${nl%q}
            
            IFS=$nl
            
            for i in $(printf '%b' 'this is line 1\nthis is line 2'); do
                echo "i=$i"
            done
            
            # Desired output:
            # i=this is line 1
            # i=this is line 2
            
            # Exercise:
            # Try running this example without the IFS=$nl assignment, and predict the outcome.
            

            【讨论】:

              【解决方案10】:

              在我的系统 (Ubuntu 17.10) 上,无论是从命令行输入(输入到 sh)还是作为 sh 脚本执行时,您的示例都可以正常工作:

              [bash]§ sh
              $ STR="Hello\nWorld"
              $ echo $STR
              Hello
              World
              $ exit
              [bash]§ echo "STR=\"Hello\nWorld\"
              > echo \$STR" > test-str.sh
              [bash]§ cat test-str.sh 
              STR="Hello\nWorld"
              echo $STR
              [bash]§ sh test-str.sh 
              Hello
              World
              

              我想这回答了你的问题:它确实有效。 (我还没有试图弄清楚细节,例如在 sh 中确切地用换行符替换 \n 发生在什么时候。

              但是,我注意到,当使用bash 执行相同的脚本时,其行为会有所不同,而是会打印出Hello\nWorld

              [bash]§ bash test-str.sh
              Hello\nWorld
              

              我已经设法通过bash 获得所需的输出,如下所示:

              [bash]§ STR="Hello
              > World"
              [bash]§ echo "$STR"
              

              注意$STR 周围的双引号。如果保存并作为bash 脚本运行,则其行为相同。

              以下也给出了所需的输出:

              [bash]§ echo "Hello
              > World"
              

              【讨论】:

                【解决方案11】:

                那些只需要换行并鄙视破坏缩进的多行代码的挑剔者可以这样做:

                IFS="$(printf '\nx')"
                IFS="${IFS%x}"
                

                Bash(以及可能的其他 shell)在命令替换后会吞噬所有尾随的换行符,因此您需要以非换行符结束 printf 字符串,然后将其删除。这也可以很容易地成为一个单行者。

                IFS="$(printf '\nx')" IFS="${IFS%x}"
                

                我知道这是两个动作而不是一个动作,但是我的缩进和可移植性强迫症现在很平静 :) 我最初开发它是为了能够拆分仅换行符分隔的输出,最后我使用了使用 @987654324 的修改@ 作为终止字符。这使得换行符拆分即使对于以 \r\n 结尾的 dos 输出也有效。

                IFS="$(printf '\n\r')"
                

                【讨论】:

                  【解决方案12】:

                  我对这里的任何选项都不满意。这对我有用。

                  str=$(printf "%s" "first line")
                  str=$(printf "$str\n%s" "another line")
                  str=$(printf "$str\n%s" "and another line")
                  

                  【讨论】:

                  • str=$(printf '%s\n' "first line" "another line" "and another line") 是一种非常乏味的方式(shell 将方便地(或不)从命令替换中删除任何最终的换行符)。这里的几个答案已经以各种形式宣传printf,所以我不确定这会增加任何价值作为单独的答案。
                  • 它适用于短字符串,但如果你想让事情保持整洁并且每行实际上是 60 个字符长,那么这是一种整洁的方式。鉴于这是我最终采用的解决方案,它确实增加了一些东西,如果不是为你,那么为我自己在将来毫无疑问返回时。
                  • 即使是长字符串,我也更喜欢printf '%s\n' "first line" \ (换行符,缩进)'second line' 等。另请参阅printf -v str,了解如何在不生成子shell 的情况下分配给变量。
                  【解决方案13】:

                  这并不理想,但我编写了很多代码并以与问题中使用的方法类似的方式定义了字符串。接受的解决方案要求我重构大量代码,因此我将每个 \n 替换为 "$'\n'",这对我有用。

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 2020-05-10
                    • 2020-10-06
                    • 2011-10-08
                    • 1970-01-01
                    • 1970-01-01
                    • 2014-12-01
                    相关资源
                    最近更新 更多