如何从命令行将每两行合并为一个?


151

我有一个以下格式的文本文件。第一行是“ 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

如何将两行合并为一行?


有很多方法可以做到这一点!我做了一个小板凳prpasteawkxargssedpure bash!(xargsbash慢,慢!)
F. Hauri

Answers:


182

awk:

awk 'NR%2{printf "%s ",$0;next;}1' yourFile

注意,输出末尾有一个空行。

sed:

sed 'N;s/\n/ /' yourFile

不适用于彩色输出。我在此问答中尝试了所有方法,但是当输出为彩色时却无济于事。在Ubuntu 13.04上测试
Leo Gallucci

1
@elgalu:因为ANSI颜色只是一串转义字符组合。对这样的输出进行hexedit,看看有什么。
not2qubit 2014年

7
如果在中找到printf类似扩展字符串,则该awk解决方案可能会中断。这样的失败可以避免:%s$0'NR%2{printf "%s ",$0;next;}1'
ghoti 2014年

9
由于Google真的很难Google,1大括号后的含义是什么?
erikbwork 2015年


243

paste 对这项工作有好处:

paste -d " "  - - < filename

10
我认为这是提出的最好的解决方案,尽管既不使用sed也不使用awk。在输入的行数为奇数时,Kent的awk解决方案跳过最后一行,他的sed解决方案跳过整个行的最后一行,而我的解决方案重复最后一行。 paste另一方面,表现良好。+1。
ghoti 2014年

8
我经常使用cut但总是忘记paste。解决这个问题是很困难的。我需要合并stdin的所有行,并使用轻松完成paste -sd ' ' -
克林特·帕奇

4
简单而美丽!
krlmlr 2014年

8
所以-意思是stdin,所以paste - -意思是从stdin读取,然后从stdin读取,您可以按照我的期望堆叠任意多个。
ThorSummoner

1
是的,@ ThorSummoner ...我必须将每三行粘贴到一行中,并且确实粘贴了---效果很好。
丹尼尔·戈德法布

35

替代sed,awk,grep:

xargs -n2 -d'\n'

当您想连接N行并且仅需要用空格分隔的输出时,这是最佳方法。

我最初的答案是xargs -n2分开单词而不是行。-d可用于按任意单个字符分割输入。


4
这是一个很好的方法,但是它适用于单词而不是行。要使其在线运行,可以添加-d '\n'
Don Hatch

2
哇,我是普通xargs用户,但不知道这一点。大提示。
Sridhar Sarnobat

1
我喜欢这个。好干净
亚历山大·郭

28

杀死狗的方法比悬挂还多。[1]

awk '{key=$0; getline; print key ", " $0;}'

将任何您喜欢的定界符放在引号中。


参考文献:

  1. 最初是“用多种方法给猫咪剥皮”,后来又恢复了一种古老的,可能起源于动物的表情,这种表情也与宠物无关。

我喜欢这个解决方案。
luis.espinal

5
作为猫的主人,我不喜欢这种幽默。
witkacy26 2015年

4
@ witkacy26,根据您的关注调整表情。
ghoti 2015年

我喜欢这个awk解决方案,但我不知道它是如何工作的:S
Rubendob

@Rubendob -awk读取输入的每一行,并将其放在变量中$0。该getline命令还将获取输入的“下一个”行并将其放入中$0。因此,第一条语句抓住了第一行,然后print命令将保存在变量中的内容key与包含逗号的字符串以及使用提取的行连接起来getline。更清晰?:)
ghoti

12

这是我在bash中的解决方案:

while read line1; do read line2; echo "$line1, $line2"; done < data.txt

11

尽管以前的解决方案似乎可以解决问题,但是如果文档中发生单个异常,则输出将变成碎片。下面比较安全。

sed -n '/KEY/{
N
s/\n/ /p
}' somefile.txt

3
为什么更安全?怎么/KEY/办?最后p做什么?
斯图尔特

/KEY/搜索与线KEY。在p打印出结果了。这是更安全的方法,因为它仅将操作应用于其中包含a的行KEY
minghua

11

这是另一种方法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

正如Ed Morton在评论中指出的那样,最好添加大括号以增加安全性,并增加可移植性。

awk '{ ORS = (NR%2 ? FS : RS) } 1' file

ORS代表输出记录分隔符。我们在这里所做的是使用NR存储行号的来测试条件。如果的模为NR真值(> 0),则将输出字段分隔符设置为FS(字段分隔符)的值,默认情况下该值为空格,否则我们将赋值为RS(记录分隔符)为换行符。

如果要添加,为分隔符,请使用以下命令:

awk '{ ORS = (NR%2 ? "," : RS) } 1' file

1
肯定是正确的方法,所以+1,但是我不知道正在评估什么条件以调用打印记录的默认操作。作业成功了吗?这是否简单,ORS并且true由于ORS获得的值不为零或为空字符串而被当作是字符串而不是数字比较来正确地猜测,因此被视为吗?还有吗 我确实不确定,所以我会写成awk '{ORS=(NR%2?FS:RS)}1' file。我也将三元表达式括起来以确保可移植性。
Ed Morton 2014年

1
@EdMorton是的,我只是看到几个对此答案的赞扬即将更新,以包括括号以确保安全。还将添加括号。
jaypal singh 2014年

7

“ ex”是一个可编写脚本的行编辑器,与sed,awk,grep等位于同一家族中。我认为这可能是您想要的。许多现代的vi克隆/后继产品也都具有vi模式。

 ex -c "%g/KEY/j" -c "wq" data.txt

这说的每一行,如果匹配“KEY”执行Ĵ OIN以下行的。该命令完成(对所有线)后,发出W¯¯仪式和q UIT。


4

如果可以选择Perl,则可以尝试:

perl -0pe 's/(.*)\n(.*)\n/$1 $2\n/g' file.txt

是否-0让Perl设置记录分隔符($/)为空,这样我们就可以弥补我们匹配模式多行手册页是有点太技术,我要弄清楚这是什么意思在实践中。
斯瑞达Sarnobat

4

您可以像这样使用awk组合两条线:

awk '{ if (NR%2 != 0) line=$0; else {printf("%s %s\n", line, $0); line="";} } \
     END {if (length(line)) print line;}' flle

4

另一种使用vim的解决方案(仅供参考)。

解决方案1

在vim中打开文件vim filename,然后执行命令:% normal Jj

这个命令很容易理解:

  • %:对于所有行,
  • 正常:执行正常命令
  • Jj:执行Join命令,然后跳到下面的行

之后,保存文件并退出 :wq

解决方案2

在shell中执行命令vim -c ":% normal Jj" filename,然后保存文件并使用退出:wq


在重新映射的情况下也norm!更加强大。+1为vim解决方案。normalJ
qeatzy

@qeatzy谢谢您教我这一点。很高兴知道这一点。^ _ ^
Jensen

3

您也可以使用以下vi命令:

:%g/.*/j

甚至:%g//j,因为所有你需要的是一个匹配的连接将被执行,而空字符串仍然是一个有效的正则表达式。
ghoti

1
@ghoti,在Vim中,当只使用时//,将使用以前的搜索模式。如果没有以前的模式,Vim只会报告一个错误,什么也不做。Jdamian的解决方案始终有效。
Tzunghsing David Wong

1
@TzunghsingDavidWong-这是vim用户的好指针。对我来说,问题和答案都没有提到vim。
ghoti

3

glenn jackman的答案略有不同paste:使用以下命令:如果-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

不能做的是使用由多个字符组成的分隔符。

另外,如果pastePOSIX兼容,则不会修改文件最后一行的换行符,因此对于输入文件而言,如奇数行

KEY 4048:1736 string
3
KEY 0:1772 string

paste 不会在最后一行加上分隔符:

$ paste -s -d ',\n' infile
KEY 4048:1736 string,3
KEY 0:1772 string

1
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

1

如果我需要合并两行(以便于处理),但允许数据超出特定范围,我发现这很有用

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

输出如下:

convert_data.txt

string1=x string2=y
string3
string4

1

使用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

0

最简单的方法是在这里:

  1. 删除偶数行并将其写入某些临时文件1。
  2. 删除奇数行并将其写入某些临时文件2。
  3. 通过将粘贴命令与-d组合使用,将两个文件合二为一(意味着删除空间)

sed '0~2d' file > 1 && sed '1~2d' file > 2 && paste -d " " 1 2

0
perl -0pE 's{^KEY.*?\K\s+(\d+)$}{ $1}msg;' data.txt > data_merged-lines.txt

-0吞噬整个文件,而不是逐行读取;
pE使用循环包装代码并打印输出,请参见http://perldoc.perl.org/perlrun.html中的详细信息;
^KEY在行的开头匹配“ KEY”,然后.*?在序列的前进行非贪婪匹配()

  1. 一个或多个\s+任何形式的空格,包括换行符;
  2. (\d+)我们捕获到的一个或多个数字,然后重新插入为$1

接下来是行尾$

\K方便地将其左侧的所有内容排除在替换之外,因此{ $1}仅替换1-2个序列,请参见http://perldoc.perl.org/perlre.html


0

一种更通用的解决方案(允许加入多个后续行)作为shell脚本。因为我需要可见性,所以在每条之间都增加了一条线,但这很容易解决。在此示例中,“关键”行以:结尾,而没有其他行。

#!/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

-1

尝试以下行:

while read line1; do read line2; echo "$line1 $line2"; done <old.txt>new_file

将定界符置于中间

"$line1 $line2";

例如,如果定界符为|,则:

"$line1|$line2";

此答案未添加Hai Vu的答案中未提供的任何内容,该答案发布于您的答案之前4年。
fedorqui'SO停止伤害

我部分同意,我尝试添加说明和更通用的内容。它也不会编辑旧文件。感谢您的建议
-Suman

-2

您可以这样使用xargs

xargs -a file

%cat>文件abc%xargs -a文件abc%对我
有用

是的,它可以执行某些操作,但是OP不会执行此操作。具体来说,它连接了尽可能多的行。您实际上可以得到想要的东西,xargs -n 2但是这个答案根本不能解释这个问题。
tripleee
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.