如何从命令行将每两行合并为一行?
Posted
技术标签:
【中文标题】如何从命令行将每两行合并为一行?【英文标题】:How to merge every two lines into one from the command line? 【发布时间】:2012-03-25 04:51:31 【问题描述】:我有一个格式如下的文本文件。第一行是“KEY”,第二行是“VALUE”。
KEY 4048:1736 string
3
KEY 0:1772 string
1
KEY 4192:1349 string
1
KEY 7329:2407 string
2
KEY 0:1774 string
1
我需要与键在同一行中的值。所以输出应该是这样的......
KEY 4048:1736 string 3
KEY 0:1772 string 1
KEY 4192:1349 string 1
KEY 7329:2407 string 2
KEY 0:1774 string 1
如果我可以使用诸如$
或,
之类的分隔符会更好:
KEY 4048:1736 string , 3
如何将两行合二为一?
【问题讨论】:
有很多方法可以做到这一点!我已经完成了little bench withpr
, paste
, awk
, xargs
, sed
and pure bash
! (xargs
比较慢,比bash 慢!)
【参考方案1】:
sed、awk、grep 的替代品:
xargs -n2 -d'\n'
当您想要连接 N 行并且只需要以空格分隔的输出时,这是最好的。
我最初的答案是xargs -n2
,它以单词而不是行分隔。 -d
(GNU xargs 选项)可用于按任何单数字符分割输入。
【讨论】:
【参考方案2】:使用 vim 的另一种方法是:
:g/KEY/join
这会将join
(到它下面的行)应用于其中包含单词KEY
的所有行。结果:
KEY 4048:1736 string 3
KEY 0:1772 string 1
KEY 4192:1349 string 1
KEY 7329:2407 string 2
KEY 0:1774 string 1
【讨论】:
【参考方案3】:使用vim的另一种解决方案(仅供参考)。
解决方案 1:
在vim中打开文件vim filename
,然后执行命令:% normal Jj
这个命令很容易理解:
% : 对于所有行, 正常:执行正常命令 Jj : 执行 Join 命令,然后跳转到下一行之后,保存文件并使用:wq
退出
解决方案 2:
在shell中执行命令vim -c ":% normal Jj" filename
,然后保存文件并以:wq
退出。
【讨论】:
还有norm!
比normal
更健壮,以防J
被重新映射。 +1 为 vim 解决方案。
@qeatzy 谢谢你教我。很高兴知道。 ^_^【参考方案4】:
在我需要合并两行(以便于处理)但允许数据超过特定的情况下,我发现这很有用
data.txt
string1=x
string2=y
string3
string4
cat data.txt | nawk '$0 ~ /string1=/ printf "%s ", $0; getline; printf "%s\n", $0; getline print ' > converted_data.txt
输出如下:
converted_data.txt
string1=x string2=y
string3
string4
【讨论】:
【参考方案5】:使用paste
与glenn jackman's answer 的细微差别:如果-d
分隔符选项的值包含多个字符,paste
将逐个循环字符,并与-s
选项结合使用在处理相同的输入文件时继续这样做。
这意味着我们可以使用任何我们想要的分隔符加上转义序列\n
来一次合并两行。
使用逗号:
$ paste -s -d ',\n' infile
KEY 4048:1736 string,3
KEY 0:1772 string,1
KEY 4192:1349 string,1
KEY 7329:2407 string,2
KEY 0:1774 string,1
和美元符号:
$ paste -s -d '$\n' infile
KEY 4048:1736 string$3
KEY 0:1772 string$1
KEY 4192:1349 string$1
KEY 7329:2407 string$2
KEY 0:1774 string$1
这个不能做的是使用由多个字符组成的分隔符。
作为奖励,如果 paste
符合 POSIX,这不会修改文件中最后一行的换行符,因此对于具有奇数行的输入文件,如
KEY 4048:1736 string
3
KEY 0:1772 string
paste
不会在最后一行添加分隔符:
$ paste -s -d ',\n' infile
KEY 4048:1736 string,3
KEY 0:1772 string
【讨论】:
【参考方案6】:awk:
awk 'NR%2printf "%s ",$0;next;1' yourFile
注意,输出的末尾有一个空行。
sed:
sed 'N;s/\n/ /' yourFile
【讨论】:
【参考方案7】:你可以像这样使用xargs
:
xargs -a file
【讨论】:
% cat > file a b c % xargs -a file a b c % 为我工作 它做了某事,是的,但不是 OP 要求的。具体来说,它连接尽可能多的行。你实际上可以用xargs -n 2
得到你想要的,但这个答案根本没有解释这一点。【参考方案8】:
试试下面这行:
while read line1; do read line2; echo "$line1 $line2"; done <old.txt>new_file
将分隔符放在中间
"$line1 $line2";
例如如果分隔符是|
,那么:
"$line1|$line2";
【讨论】:
这个答案没有添加任何未在Hai Vu's answer 中提供的内容,这些内容是在您之前 4 年发布的。 我部分同意,我尝试添加解释和更通用的它也不会编辑旧文件。感谢您的建议【参考方案9】:杀死狗的方法比绞死更多。 [1]
awk 'key=$0; getline; print key ", " $0;'
将你喜欢的任何分隔符放在引号内。
参考资料:
-
最初是“给猫剥皮的多种方法”,后来恢复为更古老的、可能起源于与宠物无关的表达方式。
【讨论】:
【参考方案10】:一种更通用的解决方案(允许加入多个后续行)作为 shell 脚本。这在每个之间添加了一条线,因为我需要可见性,但这很容易解决。此示例是“key”行以 : 结尾的地方,而其他行则没有。
#!/bin/bash
#
# join "The rest of the story" when the first line of each story
# matches $PATTERN
# Nice for looking for specific changes in bart output
#
PATTERN='*:';
LINEOUT=""
while read line; do
case $line in
$PATTERN)
echo ""
echo $LINEOUT
LINEOUT="$line"
;;
"")
LINEOUT=""
echo ""
;;
*) LINEOUT="$LINEOUT $line"
;;
esac
done
【讨论】:
【参考方案11】:perl -0pE 's^KEY.*?\K\s+(\d+)$ $1msg;' data.txt > data_merged-lines.txt
-0
吞噬整个文件,而不是逐行读取;pE
使用循环包装代码并打印输出,详情请参阅 http://perldoc.perl.org/perlrun.html;^KEY
匹配 " KEY" 在行的开头,然后在
.*?
)
-
一个或多个空格
\s+
,包括换行符;
一个或多个数字 (\d+)
我们捕获并稍后重新插入为 $1
;
紧随$
行的结尾。
\K
方便地将其左侧的所有内容排除在替换之外,因此 $1
仅替换 1-2 序列,请参阅 http://perldoc.perl.org/perlre.html。
【讨论】:
【参考方案12】:你也可以使用下面的vi命令:
:%g/.*/j
【讨论】:
甚至是:%g//j
,因为您只需要匹配要执行的 join,并且空字符串仍然是有效的正则表达式。
@ghoti,在 Vim 中,当仅使用 //
时,将使用之前的搜索模式。如果没有先前的模式,Vim 只是简单地报告一个错误并且什么都不做。 Jdamian 的解决方案一直有效。
@TzunghsingDavidWong - 这对 vim 用户来说是一个很好的指针。对我来说很方便,这个问题和这个答案都没有提到 vim。【参考方案13】:
最简单的方法在这里:
-
删除偶数行并将其写入某个临时文件 1。
删除奇数行并将其写入某个临时文件 2。
使用带有-d的粘贴命令将两个文件合二为一(表示删除空格)
sed '0~2d' file > 1 && sed '1~2d' file > 2 && paste -d " " 1 2
【讨论】:
【参考方案14】:这是awk
的另一种方式:
awk 'ORS=NR%2?FS:RS' file
$ cat file
KEY 4048:1736 string
3
KEY 0:1772 string
1
KEY 4192:1349 string
1
KEY 7329:2407 string
2
KEY 0:1774 string
1
$ awk 'ORS=NR%2?FS:RS' file
KEY 4048:1736 string 3
KEY 0:1772 string 1
KEY 4192:1349 string 1
KEY 7329:2407 string 2
KEY 0:1774 string 1
正如 cmets 中的 Ed Morton 所指出的那样,最好添加大括号以确保安全,并添加括号以提高便携性。
awk ' ORS = (NR%2 ? FS : RS) 1' file
ORS
代表输出记录分隔符。我们在这里所做的是使用存储行号的NR
测试条件。如果NR
的模为真值(>0),那么我们将输出字段分隔符设置为FS
(字段分隔符)的值,默认为空格,否则我们分配RS
的值(记录分隔符)这是换行符。
如果您希望添加,
作为分隔符,请使用以下内容:
awk ' ORS = (NR%2 ? "," : RS) 1' file
【讨论】:
绝对是正确的方法,所以 +1 但我想知道正在评估什么条件以调用打印记录的默认操作。是不是任务成功了?它只是ORS
并且被视为true
,因为ORS 获得的值不是零或空字符串并且awks 正确猜测它应该是一个刺痛而不是数字比较?是别的吗?我真的不确定,所以我会把它写成awk 'ORS=(NR%2?FS:RS)1' file
。我也将三元表达式括起来以确保可移植性。
@EdMorton 是的,我刚刚看到有人对这个答案表示赞同,即将对其进行更新以包括安全括号。也会添加括号。【参考方案15】:
"ex" 是一个可编写脚本的行编辑器,它与 sed、awk、grep 等属于同一家族。我想它可能正是您要找的。许多现代 vi 克隆/继承者也有 vi 模式。
ex -c "%g/KEY/j" -c "wq" data.txt
这表示对于每一行,如果它与“KEY”匹配,则执行下一行的 j oin。在该命令完成后(针对所有行),发出 w 仪式和 q uit。
【讨论】:
【参考方案16】:nawk '$0 ~ /string$/ printf "%s ",$0; getline; printf "%s\n", $0' filename
这读作
$0 ~ /string$/ ## matches any lines that end with the word string
printf ## so print the first line without newline
getline ## get the next line
printf "%s\n" ## print the whole line and carriage return
【讨论】:
【参考方案17】:尽管以前的解决方案似乎可行,但如果文档中出现单个异常,则输出将变得支离破碎。下面比较安全。
sed -n '/KEY/
N
s/\n/ /p
' somefile.txt
【讨论】:
为什么更安全?/KEY/
是做什么的? p
最后做了什么?
/KEY/
搜索带有KEY
的行。 p
打印出结果。它更安全,因为它只在其中包含 KEY
的行上应用操作。【参考方案18】:
这是我在 bash 中的解决方案:
while read line1; do read line2; echo "$line1, $line2"; done < data.txt
【讨论】:
【参考方案19】:你可以像这样使用 awk 来组合两对线:
awk ' if (NR%2 != 0) line=$0; else printf("%s %s\n", line, $0); line=""; \
END if (length(line)) print line;' flle
【讨论】:
【参考方案20】:paste
很适合这份工作:
paste -d " " - - < filename
【讨论】:
一个关于参数的描述将是一个很好的补充【参考方案21】:如果 Perl 是一个选项,您可以尝试:
perl -0pe 's/(.*)\n(.*)\n/$1 $2\n/g' file.txt
【讨论】:
-0
是否告诉 perl 将记录分隔符 ($/)
设置为 null,以便我们可以在匹配模式中跨越多行。联机帮助页对我来说有点太技术性了了解它在实践中的含义。以上是关于如何从命令行将每两行合并为一行?的主要内容,如果未能解决你的问题,请参考以下文章