bash 中的字符串连接导致替换

concatenation of strings in bash results in substitution

我需要将文件读入数组并在每行末尾连接一个字符串。这是我的 bash 脚本:

#!/bin/bash

IFS=$'\n' read -d '' -r -a lines < ./file.list
for i in "${lines[@]}"
do
    tmp="$i"
    tmp="${tmp}stuff"
    echo "$tmp"
done

但是,当我这样做时,发生了 replace 的动作,而不是串联。

例如,在file.list中,我们有:

http://www.example1.com
http://www.example2.com

我需要的是:

http://www.example1.comstuff
http://www.example2.comstuff

但是在执行上面的脚本之后,我在终端上得到如下信息:

stuff//www.example1.com
stuff//www.example2.com

顺便说一句,我的电脑是 Mac OS.

通过 awkprintfecho 命令连接字符串时也会出现此问题。例如 echo $tmp"stuff"echo "${tmp}""stuff"

如果你需要修改一个流来附加一个固定的字符串,你可以使用sedawk,例如:

sed 's/$/stuff/'

将内容附加到每行的末尾。

我猜这与 Carriage Return 字符有关。 您的 file.list 是在 windows 上创建的吗?如果是这样,请尝试在 运行 脚本之前使用 dos2unix

编辑

您可以使用 file 命令检查您的文件。

示例:

file file.list

如果您将文件保存在 Windows 记事本中,如下所示:

那么大概会出现这样的:

  • file.list: ASCII text, with no line terminators 

您可以使用 iconv 等内置工具来转换编码。但是,对于像这样的简单使用,您可以只使用 适用于多种编码 的命令,而无需任何转换。

您可以简单地通过 cat 缓冲文件,并使用适用于以下任一的正则表达式:

  • 回车符 return 后跟行终止符,或
  • 它自己的行终止符

然后附加字符串。

示例:

cat file.list | grep -E -v "^$" | sed -E -e "s/(\r?$)/stuff/g" 

将使用 ASCII 文本和没有行终止符的 ASCII 文本。

文件 ./file.lst 很可能是在 Windows 系统上生成的,或者至少是使用 Windows 行尾约定保存的。

Windows 使用两个字符的序列来标记文本文件中行的结尾。这些字符是 CR (\r) followed by LF (\n)。类 Unix 系统(Linux 和从版本 10 开始的 macOS)使用 LF 作为行尾字符。

代码中 read 前面的赋值 IFS=$'\n' 告诉 read 使用 LF 作为行分隔符。 read 不会将 LF 字符存储在它生成的数组中 (lines[]),但是 lines[] 中的每个条目都以 CR 字符结尾。

tmp="${tmp}stuff" 做了它应该做的事情,即它将单词 stuff 附加到变量 tmp 的内容(从文件中读取的一行)。

从输入文件中读取的第一行包含字符串 http://www.example1.com,后跟 CR 字符。追加字符串stuff后,变量tmp的内容为:

http://www.example1.com$'\r'stuff

CR 字符不可打印。它在终端上打印时有一个特殊的解释:它在行的开头(第 1 列)发送光标而不更改行。

echo打印上面的行时,它打印(从新行开始)http://www.example1.com,然后是 CR 字符,将光标发送回行首where is 打印字符串 stuffstuff 片段覆盖了该行 (http:) 上已打印的前 5 个字符,结果在屏幕上显示为:

stuff//www.example1.com

解决方案是从输入文件中删除 CR 个字符。有几种方法可以实现这个目标。

从输入文件中删除 CR 个字符的简单方法是使用命令:

sed -i.bak s/$'\r'//g file.list

它从文件 file.list 的内容中删除所有 CR 字符,将更新后的字符串保存回 file.list 文件并将原始 file.list 文件存储为file.list.bak(一个备份副本,以防它没有产生您期望的输出)。

另一种删除 CR 字符的方法是要求 shell 在附加 stuff 的命令中将其删除:

tmp="${tmp/$'\r'/}stuff"

当一个变量在像${tmp/a/b}这样的结构中展开时,$tmp中出现的所有a都被替换为b。在这种情况下,我们将 \r 替换为空