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.
通过 awk
、printf
和 echo
命令连接字符串时也会出现此问题。例如 echo $tmp"stuff"
或 echo "${tmp}""stuff"
如果你需要修改一个流来附加一个固定的字符串,你可以使用sed
或awk
,例如:
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 打印字符串 stuff
。 stuff
片段覆盖了该行 (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
替换为空
我需要将文件读入数组并在每行末尾连接一个字符串。这是我的 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.
通过 awk
、printf
和 echo
命令连接字符串时也会出现此问题。例如 echo $tmp"stuff"
或 echo "${tmp}""stuff"
如果你需要修改一个流来附加一个固定的字符串,你可以使用sed
或awk
,例如:
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 打印字符串 stuff
。 stuff
片段覆盖了该行 (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
替换为空