如何在Bash中从字符串中删除换行符

174

我有以下变量。

echo "|$COMMAND|"

返回

|
REBOOT|

如何删除第一个换行符?

12个回答

183
在字符串中,字符替换/删除
下,有一些bashisms: tr命令可以被${parameter/pattern/string}替换,这是一种bashism。
COMMAND=$'\nREBOOT\r   \n'
echo "|${COMMAND}|"
|
   OOT
|

echo "|${COMMAND//[$'\t\r\n']}|"
|REBOOT   |

echo "|${COMMAND//[$'\t\r\n ']}|"
|REBOOT|

请参阅bash的man页中的参数扩展引用部分。
man -Pless\ +/parameter/pattern/string bash

man -Pless\ +/\/pattern bash
man -Pless\ +/\\\'string\\\' bash

man -Pless\ +/^\\\ *Parameter\\\ Exp bash
man -Pless\ +/^\\\ *QUOTING bash
${parameter//pattern/string}
    If there are two slashes separating parameter and pattern,
    all  matches of pattern are replaced with string.
进一步...
如@AlexJordan所要求,这将抑制所有指定的字符。那么如果$COMMAND包含空格呢...
COMMAND=$'         \n        RE BOOT      \r           \n'
echo "|$COMMAND|"
|
           BOOT      
|

read -r COMMAND <<<"${COMMAND//[$'\t\r\n']}"
echo "|$COMMAND|"
|RE BOOT|

解释

回答Vulwsztyn的问题

为什么当模式为空时,这个方法能够工作?

${COMMAND//[$'\t\r\n ']} 中:

  • 第一个斜杠 / 的意思是:模式替换(遵循 ${parameter/pattern/string} 的语法)
  • 模式是 /[$'\r\n '],以 / 开头,然后将所有匹配的模式替换为 string
  • 然后,替换的 string 是空的(因为没有第二个斜杠跟随任何 string...)。

再进一步

如果你试图用某些东西替换nothing,例如两个连续的空格(然后你可以在替换的字符串后面再添加两个空格以平衡输出):
echo "|${COMMAND//*()/  }  |"
|  R  E     B  O  O  T  |

避免将单个字符串用fork转换为tr!
让我们来比较一下:
COMMAND=$'\nREBOOT\r   \n'
echo ${COMMAND@Q}
$'\nREBOOT\r \n'

COMMAND=$(echo $COMMAND|tr -d '\n\t\r ')
echo ${COMMAND@Q}
'REBOOT'

那么

time for i in {1..1000};do
    COMMAND=$'\nREBOOT\r   \n'
    COMMAND=$(echo $COMMAND|tr -d '\n\t\r ')
done;echo ${COMMAND@Q}

real    0m2.785s
user    0m2.296s
sys     0m0.774s
'REBOOT'

COMMAND=$'\nREBOOT\r   \n'
COMMAND="${COMMAND//[$'\t\r\n ']}"
echo ${COMMAND@Q}

time for i in {1..1000};do
    COMMAND=$'\nREBOOT\r   \n'
    COMMAND="${COMMAND//[$'\t\r\n ']}"
done;echo ${COMMAND@Q}

real    0m0.006s
user    0m0.001s
sys     0m0.004s
'REBOOT'

在我的主机上,使用1,000个fork来执行tr需要超过2700毫秒的时间,而使用内置的bash参数扩展相同的任务只需要6毫秒(快了464.2倍!)。

注意:实际上:var=$(echo | tr x y)会导致两个fork,而不仅仅是一个!通过使用以下语法,您可以避免1个(x1000)fork,从而使速度稍微更快:

time for i in {1..1000};do
    COMMAND=$'\nREBOOT\r   \n'
    COMMAND=$( tr -d '\n\t\r ' <<<"$COMMAND" )
done;echo ${COMMAND@Q}

real    0m2.181s
user    0m1.590s
sys     0m0.566s
'REBOOT'

但与纯粹的bash方法相比,仍然有很多过度的部分。

1
这是在BASH中从变量中删除换行符的方法。其他答案不必要地调用了额外的TR进程。顺便说一句,它还有去除末尾空格的附加好处! - ingyhere
2
请注意,它还会从字符串中删除内部空格... COMMAND="RE BOOT"; echo "|${COMMAND//[$'\t\r\n ']}|" 返回 |REBOOT| - Alex Jordan
2
@AlexJordan 是的,这是一个期望的功能:您可以擦除\n后面的空格以防止出现此问题:COMMAND="RE BOOT"; echo "|${COMMAND//[$'\t\r\n']}|"将返回|RE BOOT| - F. Hauri - Give Up GitHub
4
${COMMAND//[$'\t\r\n']}中的模式是如何起作用的?我原以为${COMMAND//[\t\r\n]}就可以了,但实际上并没有。$符号和单引号又有什么作用呢? - haridsv
1
关于$''语法,那是[ANSI C引用](https://www.gnu.org/software/bash/manual/bash.html#ANSI_002dC-Quoting)(wjordans的回答中提到)。 - chuckx
显示剩余7条评论

122

通过删除所有换行符来清理您的变量:

COMMAND=$(echo $COMMAND|tr -d '\n')

29
那不会去掉换行符吗?应该改成 tr -d '\r' 才对吧? - Izzy
6
未注释的变量在回显时会删除所有IFS字符(默认情况下为换行符、空格和制表符)。因此,如果您要这样做,应该知道所有 IFS字符都会被删除,而且您不需要 tr 命令。只需执行 COMMAND=$(echo $COMMAND) 即可获得类似的效果。这可能会产生新的进程,看起来有些麻烦,但对于人眼来说很简短清晰,如果你能抽出一两秒的时间,也许愿意尝试一下 :-)。 - Mike S
1
我已经更新了问题,将其更改为“换行符”,因为它的示例确实显示它是一个换行符,而不是回车符。这个答案仍然是正确的,但也许应该更新一下,用“换行符”代替“回车符”? - Benjamin W.
1
应该被接受的答案是结合Bash特定的${COMMAND//[$'\t\r\n']} - Summer-Sky
1
@MikeS 注意到反模式 echo $COMMAND (缺少引号) 的问题,点赞+1。但是_这是一个反模式,不应该使用_,因为如果存在任何全局通配符(例如 *[...]?),它们也会被扩展。所以除非你的变量实际上是一个你想要扩展的通配符,否则永远不要使用未引用的扩展。 - gniourf_gniourf

108
echo "|$COMMAND|"|tr '\n' ' '

将换行符(在POSIX / Unix中不是回车符)替换为空格。

说实话,我会考虑从bash切换到更稳健的东西。或者在第一次生成这种畸形数据时避免它。

嗯,这似乎也可能是一个可怕的安全漏洞,具体取决于数据来自何处。


日期来自于在同一台服务器上的Curl请求,我该如何将其放入新变量中?newvar=$(echo "|$COMMAND|"|tr '\n' ' ') - Matt Leyland
2
是的。但请告诉我,您不会允许任意人在没有密码的情况下远程重新启动您的服务器吧... - Robin Green
51
为什么你没有使用tr -d '\n'来代替用空格替换以删除换行符? - F. Hauri - Give Up GitHub
4
请清除无用的管道!使用 tr '\n' ' ' <<< "|$COMMAND|" 替代 echo ... | ... - F. Hauri - Give Up GitHub
13
@F.Hauri: 或者无用的 tr 命令: "|${COMMAND//$'\n'}|"。意思是将变量 $COMMAND 中的所有换行符替换为空格,并用竖线 | 包围整个字符串。 - rici
显示剩余5条评论

15

使用 bash

echo "|${COMMAND/$'\n'}|"

(请注意,此问题中的控制字符是“换行符”(\n),而不是回车符(\r)。后者会在一行上输出REBOOT|。)

解释

使用 Bash 的Shell参数扩展 ${parameter/pattern/string}

模式被扩展以产生一个模式,就像在文件名扩展中一样。 参数被扩展,并将匹配模式与其值的最长匹配替换为字符串。[...] 如果字符串为空,则删除模式的匹配项,可以省略跟在模式后面的 /。

还使用了$'' ANSI-C引用 结构来指定一个换行符为$'\n'。直接使用换行符也可以,但不太美观:

echo "|${COMMAND/
}|"

完整例子

#!/bin/bash
COMMAND="$'\n'REBOOT"
echo "|${COMMAND/$'\n'}|"
# Outputs |REBOOT|

或者,使用换行:

#!/bin/bash
COMMAND="
REBOOT"
echo "|${COMMAND/
}|"
# Outputs |REBOOT|

12

添加答案以展示使用tr剥离多个字符(包括\r)和使用sed,并且使用hexdump进行说明。

在我的情况下,我发现以awk打印行中最后一项|awk '{print $2}'结束的命令包含回车符\r和引号。

我使用sed 's/["\n\r]//g'来剥离回车符和引号。

我也可以使用tr -d '"\r\n'

值得注意的是,如果希望删除\n换行符,则需要使用sed -z

$ COMMAND=$'\n"REBOOT"\r   \n'

$ echo "$COMMAND" |hexdump -C
00000000  0a 22 52 45 42 4f 4f 54  22 0d 20 20 20 0a 0a     |."REBOOT".   ..|

$ echo "$COMMAND" |tr -d '"\r\n' |hexdump -C
00000000  52 45 42 4f 4f 54 20 20  20                       |REBOOT   |

$ echo "$COMMAND" |sed 's/["\n\r]//g' |hexdump -C
00000000  0a 52 45 42 4f 4f 54 20  20 20 0a 0a              |.REBOOT   ..|

$ echo "$COMMAND" |sed -z 's/["\n\r]//g' |hexdump -C
00000000  52 45 42 4f 4f 54 20 20  20                       |REBOOT   |

这与之相关: 什么是回车、换行和换页?

  • CR == \r == 0x0d
  • LF == \n == 0x0a

10

对我有用的是 echo $testVar | tr "\n" " "

这里的 testVar 包含了我的变量/脚本输出


6

如果你不想为像(tr, sed 或 awk)这样简单的任务产生进程,那么可以使用这个Bashism。Bash可以独自完成:

COMMAND=${COMMAND//$'\n'/}

来自文档:

${FOO//from/to} Replace all
${FOO/from/to}  Replace first match

你在回答发布9年后使用那些非常bashisms,提供了出色的示例和指向文档的绝妙指针。F. Hauri在2013年给出了最佳答案,而这个答案没有任何补充,同时省略了一些不错的额外内容。抱歉,-1。 - Mike S
@MikeS 抱歉,如果我们想要编写有效的bash脚本,尽可能少地使用外部二进制执行是非常重要的。这意味着我们需要尽可能使用内部bash机制来处理事情。这是第一个也是唯一一个能够做到这一点的答案。许多活跃的bash用户并不认为外部调用有什么不好,而其他人则认为有问题。这种二元论在Unix SE上也非常典型。如果您没有强烈的倾向于调用尽可能多的外部二进制文件,我认为即使在您的意见中,这个答案也不会太差。 - peterh
我从答案中删除了“请点赞”的部分。 - peterh
@peterh,你没有看到F. Hauri的回答吗?他使用了相同的结构。例如:CLEANED=${COMMAND//[$'\t\r\n']}。我不是因为它在编写bash脚本时有效而投反对票,而是因为它出现在F. Hauri的回答之前,后者更完整,包括指向man页面等内容。它没有添加任何新内容。 - Mike S
@MikeS 嗯,我忽略了它的复杂性。我只搜索了内部命令的快速解决方案。如果你能通过Shell脚本谋生,那对你来说是很好的。 - peterh
@MikeS 嗯,我忽视了它的复杂性。我只是在寻找一个只用内部命令的快速解决方案。如果你能通过Shell脚本谋生,那对你来说是很好的。 - undefined

5
如果您启用了带有 extglob 选项的 bash,则可以通过以下方式仅删除尾随空格:
shopt -s extglob
COMMAND=$'\nRE BOOT\r   \n'
echo "|${COMMAND%%*([$'\t\r\n '])}|"

这将输出:
|
RE BOOT|

或者将%%替换为##以仅替换前导空格。


1
这个操作非常顺利,但我从一个Docker命令中得到了一些奇怪的输出。非常感谢! - develCuy

4
您可以简单地使用echo -n "|$COMMAND|"$ man echo

-n 不输出尾随换行符。


1
但是至少有一个受关注的换行符不在末尾。所以这并没有帮助。 - Mike S
恕我直言:您是否已经尝试过我的解决方案并检查它是否解决了原始问题? - Paulo
不行,因为man页面已经非常清楚了:-n选项不输出结尾的换行符。就是这样,没有更多也没有更少。如果原问题中有嵌入的换行符,那么echo -n也无法帮助解决问题。我刚刚试了一下,在bash中它的表现确实和描述的一样。嵌入的换行符仍然存在;它并不会“删除第一个换行符”,这正是原问题所询问的。 - Mike S
好的,我明白了您的观点。当我运行我建议的命令时,即使我的命令不是针对这个问题设计的,输出结果也与删除第一个命令后相同。我相信我误解了问题。 - Paulo

2

为了解决实际问题的一个可能根源,有可能你正在使用一个crlf文件。

crlf示例:

.env (crlf)

VARIABLE_A="abc"
VARIABLE_B="def"

run.sh

#!/bin/bash
source .env
echo "$VARIABLE_A"
echo "$VARIABLE_B"
echo "$VARIABLE_A $VARIABLE_B"

返回:

abc
def
 def

如果您转换为LF:

.env(lf)

VARIABLE_A="abc"
VARIABLE_B="def"

run.sh

#!/bin/bash
source .env
echo "$VARIABLE_A"
echo "$VARIABLE_B"
echo "$VARIABLE_A $VARIABLE_B"

返回:

abc
def
abc def

1
谢谢您,我已经为此烦恼了一段时间,不知道为什么变量替换会破坏我的字符串。我在Linux上使用CRLF文件... - Yonic Surny

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