Как объединить каждые две строки в одну из командной строки?


151

У меня есть текстовый файл в следующем формате. Первая строка - это «КЛЮЧ», а вторая - «ЗНАЧЕНИЕ».

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

Как мне объединить две строки в одну?


Существует много способов сделать это! Я сделал небольшую лавку с pr, paste, awk, xargs, sedиpure bash ! ( xargsмедленнее, медленнее, чем Баш !)
Ф. Хаури

Ответы:


183

AWK:

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

обратите внимание, в конце вывода есть пустая строка.

СЭД:

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

Не работает с цветным выводом. Я попробовал все на этом Q & A, и ничего не работало, когда вывод цветной. Проверено на Ubuntu 13.04
Лео Галлуччи

1
@elgalu: Потому что цвета ANSI - это просто набор комбинаций escape-символов. Сделайте hexedit на такой вывод, чтобы увидеть, что у вас есть.
not2qubit

7
Это awk-решение может сломаться, если внутри найдены printfстроки расширения, подобные . Этого сбоя можно избежать следующим образом:%s$0'NR%2{printf "%s ",$0;next;}1'
ghoti

9
Потому что это действительно трудно для Google, что означает 1после закрывающей скобки?
erikbwork

5
@ erikb85 Вот, пожалуйста, stackoverflow.com/questions/24643240/…
Viraj

243

paste хорош для этой работы:

paste -d " "  - - < filename

10
Я думаю, что это лучшее из представленных решений, несмотря на использование ни sed, ни awk. При вводе, который является нечетным числом строк, решение Kent по awk пропускает последнюю строку, его решение sed пропускает последнюю строку в конце, а мое решение повторяет последнюю строку. pasteс другой стороны, ведет себя отлично. +1.
ghoti

8
Я часто использую, cutно всегда забываю paste. Это качается для этой проблемы. Мне нужно было объединить все строки из стандартного ввода и сделать это легко paste -sd ' ' -.
Клинт Пахл

4
Просто и красиво!
krlmlr

8
так что -значит stdin, так paste - -что читайте из stdin, а затем читайте из stdin, вы можете сложить столько их, сколько хотите, как я ожидаю.
ThorSummoner

1
Да, @ThorSummoner ... Мне пришлось вставлять каждые три строки в одну строку и вставлять - - - и это работало отлично.
Даниэль Голдфарб

35

Альтернатива sed, awk, grep:

xargs -n2 -d'\n'

Это лучше всего, когда вы хотите объединить N строк и вам нужен только вывод с разделителями.

Мой оригинальный ответ был xargs -n2разделен на слова, а не на строки. -dможет использоваться для разделения ввода на любой отдельный символ.


4
Это хороший метод, но он работает на словах, а не на строках. Чтобы заставить его работать по строкам, можно добавить-d '\n'
Дон Хэтч

2
Вау, я обычный xargsпользователь, но не знал этого. Отличный совет.
Шридхар Сарнобат

1
Мне это нравится. Так чисто.
Александр Го

28

Есть больше способов убить собаку, чем повесить. [1]

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

Поместите любой разделитель в кавычки.


Ссылки:

  1. Первоначально «Множество способов снять шкуру с кошки», возвращаясь к более старому, потенциально возникающему выражению, которое также не имеет ничего общего с домашними животными.

Я люблю это решение.
luis.espinal

5
Как владелец кошки, я не ценю этот вид юмора.
witkacy26

4
@ witkacy26, скорректированное выражение для вашей заботы.
Готи

Мне нравится это решение 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выводит результат из. это безопаснее, потому что он применяет операцию только к строкам, KEYв которых есть.
Минхуа

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

Как указал Эд Мортон в комментариях, лучше добавить скобки для безопасности и парены для переносимости.

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. Я заключил в скобки троичное выражение, чтобы обеспечить переносимость.
Эд Мортон

1
@EdMorton Да, я только что увидел пару возражений по этому ответу, который собирался обновить его, чтобы включить скобки для безопасности. Также добавлю паренсов.
Джайпал Сингх

7

«ex» - это редактор строк с возможностью написания сценариев, принадлежащий к тому же семейству, что и sed, awk, grep и т. д. Я думаю, что это может быть то, что вы ищете. Многие современные клоны / наследники vi также имеют режим vi.

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

Это говорит для каждой строки, если она соответствует «KEY» выполнить J ойн следующей строки. После этого завершения команды (против всех линий), выдавать ш обрядового и д ПИФ.


4

Если Perl является опцией, вы можете попробовать:

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

Указывает ли -0perl установить разделитель записей ( $/)на ноль, чтобы мы могли охватить несколько строк в нашем шаблоне сопоставления. Руководства слишком технические, чтобы я мог понять, что это означает на практике.
Шридхар Сарнобат,

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 :

Выполните команду в оболочке, vim -c ":% normal Jj" filenameзатем сохраните файл и выйдите с помощью :wq.


Также norm!надежнее, что normalв случае Jбыл переназначен. +1 за vim решение.
qeatzy

@qeatzy Спасибо, что научил меня этому. Очень рад это знать. ^ _ ^
Дженсен

3

Вы также можете использовать следующую команду vi:

:%g/.*/j

Или даже, :%g//jпоскольку все, что вам нужно, - это совпадение для выполняемого объединения , а нулевая строка все еще является допустимым регулярным выражением.
ghoti

1
@ghoti, в Vim, при использовании just //, вместо этого будет использоваться предыдущий шаблон поиска. Если предыдущего шаблона нет, Vim просто сообщает об ошибке и ничего не делает. Решение Дждамяна работает постоянно.
Цунхсинг Дэвид Вонг

1
@TzunghsingDavidWong - это хороший указатель для пользователей vim. Удобно для меня, ни вопрос, ни этот ответ не упомянул vim.
Готи

3

Небольшое изменение в ответе Гленна Джекмана с использованием paste: если значение для параметра -dразделителя содержит более одного символа, pasteциклически перебирает символы по одному и в сочетании с -sпараметрами продолжает делать это при обработке одного и того же входного файла.

Это означает, что мы можем использовать все, что хотим, в качестве разделителя плюс escape-последовательность \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

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

вывод тогда выглядит так:

converted_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

Более общее решение (допускает объединение нескольких последующих строк) в виде сценария оболочки. Это добавляет грань между каждым, потому что мне нужна была видимость, но это легко исправить. В этом примере строка «ключа» оканчивалась на: другие строки не делали.

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

Этот ответ не добавляет ничего, что не указано в ответе Хай Ву, который был размещен за 4 года до вашего.
Федорки "ТАК прекратить вредить"

Я согласен частично, я пытаюсь добавить объяснение и более общий. Он также не будет редактировать старый файл. Спасибо за ваше предложение
Suman

-2

Вы можете использовать xargsкак это:

xargs -a file

% cat> файл abc% xargs -a файл abc% У меня работает
RSG

Да, он что-то делает , но не то, о чем просил ОП. В частности, он объединяет столько строк, сколько возможно. Вы могли бы получить то, что вы хотите, xargs -n 2но этот ответ не объясняет это вообще.
tripleee
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.