Як об'єднати кожні два рядки в один із командного рядка?


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

Як злити два рядки в один?


Існує багато способів зробити це! Я зробив невелику лавку з pr, paste, awk, xargs, sedіpure bash ! ( xargsповільніше, повільніше, ніж баш !)
Ф. Хаурі

Відповіді:


182

awk:

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

Зауважте, в кінці виходу є порожній рядок.

sed:

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

Не працює з кольоровим виходом. Я спробував усе на цьому питанні і нічого не вийшло, коли вихідний колір кольоровий. Випробуваний на Ubuntu 13.04
Лео Галлуччі

1
@elgalu: Оскільки кольори ANSI - це лише купа поєднань символів. Зробіть гексаксит на такому виході, щоб побачити, що у вас є.
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. При введенні, що є непарною кількістю рядків, awk-рішення Кента пропускає остаточний рядок, його рішення sed пропускає остаточний рядок, а моє рішення повторює останній рядок. pasteз іншого боку, веде себе прекрасно. +1.
ghoti

8
Я часто використовую, cutале про це завжди забуваю paste. Це готується до цієї проблеми. Мені потрібно було поєднувати всі рядки зі stdin, і це було легко paste -sd ' ' -.
Клінт Пахл

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

8
так -середній STDIN, тому paste - -середнє читання зі стандартного вводу, а потім читати зі стандартного вводу, ви можете скласти так як багато хто з них , як ви хочете , я думаю.
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, Налаштоване висловлення відповідно до ваших проблем.
ghoti

Я люблю це дивовижне рішення, але не розумію, як воно працює: 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в ній.
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

Як вказав Ед Мортон у коментарях, краще додати брекети для безпеки та паролі для портативності.

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 Так, я щойно побачив пару відгуків на цю відповідь, яка збиралася оновити її, щоб включити дужки для безпеки. Також додадуть паролі.
jaypal singh

7

"ex" - це редактор рядків для сценаріїв, який знаходиться в одній родині як sed, awk, grep тощо. Я думаю, це може бути те, що ви шукаєте. Багато сучасних клонів / наступників vi також мають режим vi.

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

Це говорить для кожного рядка, якщо він відповідає "KEY", виконайте j oin наступного рядка. Після цього команда завершиться (проти всіх рядків), видайте w rite та q uit.


4

Якщо Perl - це варіант, ви можете спробувати:

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

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

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 при просто //використанні буде використаний попередній шаблон пошуку. Якщо попереднього шаблону немає, Vim просто повідомляє про помилку і нічого не робить. Рішення Джадіана працює весь час.
Цунгсінг Девід Вонг

1
@TzunghsingDavidWong - це хороший вказівник для користувачів 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

Що цього зробити не можна - це використовувати роздільник, що складається з декількох символів.

Якщо бонус, якщо 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

Тоді вихід виглядає так:

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. Об'єднайте два файли в одному за допомогою команди paste з -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Матч "КЛЮЧ" на початку рядка, а потім не жадібний матч нічого ( .*?) перед послідовністю

  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 роки до вашого.
fedorqui 'ТАК перестаньте шкодити'

Я частково погоджуюсь, я намагаюся додати пояснення і більш загальні. Він також не буде редагувати старий файл. Дякуємо за вашу пропозицію
Суман,

-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.