如何将多行加入一条
1. 概述
当我们使用 Linux 命令行时,将多行输入合并为一行是一种常见的操作。有时,我们也想为合并的行添加自定义分隔符。
在本教程中,我们将看看几种方法来做到这一点。
2. 问题
假设我们有一个纯文本输入文件:
$ cat input.txt
I came
I saw
I conquered!
该文件有三行,每行都有空格。
我们可能希望以不同的方式加入他们:
- 没有分隔符:I cameI sawI conquered!
- 带有单个字符(’,’)的分隔符:I came,I saw,I conquered!
- 带有多个字符的分隔符(’;’):I came; I saw; I conquered!
在本教程中,我们将尝试通过以下方式解决这些问题:
3. 纯bash
Bash 是大多数现代 Linux 发行版中的默认 shell,并且Bash 解决方案不依赖于其他实用程序,因为它只使用内置命令。
3.1. 不带分隔符和单个字符分隔符的连接
简短的 Bash 单行可以在没有分隔符的情况下连接行:
$ (readarray -t ARRAY < input.txt; IFS=''; echo "${ARRAY[*]}")
I cameI sawI conquered!
如果我们使用相同的脚本,但将单个字符 ’ , ‘分配给IFS变量,那么第二个问题也会得到解决:
$ (readarray -t ARRAY < input.txt; IFS=','; echo "${ARRAY[*]}")
I came,I saw,I conquered!
现在,让我们了解脚本是如何工作的。上面的单行具有三个构建块,我们将逐一介绍:
readarray -t ARRAY < input.txt
readarray 是一个 Bash 内置命令。它是在 Bash 版本 4 中引入的。 readarray将标准输入中的 行读入一个数组变量:ARRAY。-t选项将从每行中删除尾随的换行符。**之后,我们有一个包含三个元素的变量ARRAY 。
由于我们的输入数据在 input.txt文件中,我们应该 使用*< input.txt*将文件重定向到标准输入。
IFS=''
** IFS是一个特殊的shell 变量,它的名字的意思是内部字段分隔符 。IFS的默认值是空格、制表符和换行符。** 在这里,我们根据我们的要求为IFS分配了一个字符,空或“,”。
echo "${ARRAY[*]}"
** ${ARRAY[*]} 表示数组变量ARRAY的所有元素。**使用 echo命令,将打印出 ARRAY的所有元素 ,并由IFS变量分隔。换句话说,我们得到了我们需要的输出。
还有几件事我们应该注意。
我们将所有命令放在括号中。这是因为*(…commands …)在子 shell 中执行 命令,因此不会推断当前 shell中的IFS*变量。**
${ARRAY[*]} 和 ${ARRAY[@]} 都表示数组的所有元素。它们之间的区别很微妙: ${ARRAY[*]}创建一个参数,而 $ARRAY[@] 将扩展为单独的参数。IFS变量只对第一个生效
3.2. 加入多字符分隔符
使用IFS变量来控制数组输出很方便。但是,如果我们想用多个字符的分隔符来分隔元素,这种方式就行不通了。 有几种方法可以解决这个问题。由于我们已经有了一个数组变量,让我们再次使用它:
$ readarray -t ARRAY < input.txt; printf -v TXT "%s; " "${ARRAY[@]}"; echo ${TXT%; }
I came; I saw; I conquered!
让我们仔细看看这个命令并了解它是如何工作的。
printf -v TXT "%s; " "${ARRAY[@]}"
通过readarray命令获得ARRAY变量 后,我们使用带有*-v var选项的内置 printf命令将格式化字符串保存在变量$TXT* 中。
这一次,我们使用 ${ARRAY[@]} 而不是 ${ARRAY[*]} ,因为我们希望有多个参数并将每个参数传递给 printf命令。
然后*$TXT*的值是:“我来了;我看见; 我征服了!“。剩下的唯一任务是删除尾随分隔符“ ; “。
echo ${TXT%; }
** ${var%substring}是一个字符串操作技巧 。它从$var后面删除*$substring*的最短匹配。*所以${TXT%; }*将删除尾随的“ ; “。
因此,我们得到了所需的输出。
4. tr命令
我们可以使用tr命令从标准输入 (stdin) 中删除特定字符 或转换字符 。
由于tr命令只从 stdin 读取,所以当我们想使用tr处理文件时,我们应该将文件重定向到 stdin。
4.1. 无分隔符加入
tr命令可以非常直接地解决这个问题。如果我们从文件内容中删除所有换行符,所有行将连接在一起:
$ tr -d '\n' < input.txt
I cameI sawI conquered!
4.2. 加入单个字符分隔符
我们可能认为如果将所有换行符都转换为逗号“ , ” ,问题也可以轻松解决。试一试吧:
$ tr '\n' ',' < input.txt
I came,I saw,I conquered!,
哎呀!上面的输出中有一个尾随逗号。这是因为文件中的最后一行以换行符结束。不幸的是,tr命令不能删除结尾的逗号。
也就是说,tr实用程序不能单独解决这个问题。我们需要其他一些实用程序的帮助来解决它。
例如,我们可以将tr命令的输出通过管道传输到sed命令,以将结尾的逗号更改为换行符:
$ tr '\n' ',' < input.txt | sed 's/,$/\n/'
I came,I saw,I conquered!
4.3. 加入多字符分隔符
tr命令不能将 单个字符转换为多个字符,因此它不能连接具有多个字符分隔符的行。
5. paste命令
paste实用程序是GNU Coreutils 软件包的 成员,因此它适用于所有 Linux 发行版。
paste命令只做一 件事:合并文件行。这正是我们解决问题所需要的。
5.1. 不带分隔符和单个字符分隔符的连接
让我们看看如何使用paste命令解决这两个问题:
$ paste -sd '' input.txt
I cameI sawI conquered!
$ paste -sd ',' input.txt
I came,I saw,I conquered!
在上面的两个命令中,我们向paste命令传递了两个选项 : -s 和 -d。
paste命令可以合并来自多个输入文件的行。默认情况下,它以第一列中的条目属于第一个文件的方式合并行,第二列中的条目属于第二个文件,依此类推。-s选项可以让它逐行合并行。
此外,我们通过传递*-d* 或 -d , 告诉paste命令使用给定的分隔符分隔合并的行 。
5.2. 加入多字符分隔符
由于*-d选项控制结果中的分隔符。我们希望可以通过将-d与多个字符的字符串一起传递给粘贴*命令来解决该问题。让我们看看会发生什么:
$ paste -sd "@#" input.txt
I came@I saw#I conquered!
上面的测试表明,*如果我们将多个字符传递给-d*选项,paste命令会将每个字符依次转换为分隔符,而不是多个字符分隔符。**然而,这不是我们想要的。
paste命令不能连接具有多个字符分隔符的行。
6. sed命令
sed是一个强大的 命令行文本处理实用程序。我们可以使用几乎相同的代码解决这三个问题:
$ sed ':a; N; $!ba; s/\n//g' input.txt
I cameI sawI conquered!
$ sed ':a; N; $!ba; s/\n/,/g' input.txt
I came,I saw,I conquered!
$ sed ':a; N; $!ba; s/\n/; /g' input.txt
I came; I saw; I conquered!
简单地说,这个sed one-liner 的想法是:将每一行追加到模式空间 中,最后用给定的字符串替换所有换行符。 让我们了解它是如何工作的:
- :a; – 我们定义了一个名为a的标签
- N; – 将下一行添加到sed的模式空间
- $!ba; – 如果当前行是最后一行 ( $ ),不要 ( ! ) 跳转到标签*:a* ( a )
- s/\n/REPLACEMENT/g – 用给定的REPLACEMENT替换所有换行符
由于sed的s/../../g是基于正则表达式的替换,我们可以只给出不同的替换来解决我们的三个问题。
7. awk 命令
awk是另一个很棒的命令行文本处理工具。
使用awk有不同的方法可以解决我们的问题。在本节中,我们将展示其中之一:
$ awk -v d="" '{s=(NR==1?s:s d)$0}END{print s}' input.txt
I cameI sawI conquered!
$ awk -v d="," '{s=(NR==1?s:s d)$0}END{print s}' input.txt
I came,I saw,I conquered!
$ awk -v d="; " '{s=(NR==1?s:s d)$0}END{print s}' input.txt
I came; I saw; I conquered!
我们看到我们只是使用所需的分隔符设置变量d的值,相同的awk代码将为我们提供预期的结果。
让我们仔细看看代码以了解它是如何工作的:
- -vd="…" – 我们创建了一个变量 d 以便我们可以避免在代码中硬编码分隔符字符串
- s=(NR==1?s:sd)$0 – 我们连接每一行 ( $0 ) 并保存到变量s
- NR==1?s:sd – 我们处理了分隔符,因为我们不想在第一行添加分隔符作为前缀。它是if(NR>1) s=sd的紧凑形式
- END{print s} – 在处理完所有行之后,我们在*END *块中打印变量 s