Jak połączyć co dwa wiersze w jeden z wiersza poleceń?

151

Mam plik tekstowy o następującym formacie. Pierwsza linia to „KEY”, a druga to „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

Potrzebuję wartości w tym samym wierszu co klucz. Więc wynik powinien wyglądać tak ...

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

Byłoby lepiej, gdybym mógł użyć jakiegoś separatora, takiego jak $lub ,:

KEY 4048:1736 string , 3

Jak połączyć dwie linie w jedną?

shantanuo
źródło
Można to zrobić na wiele sposobów! Zrobiłem małą ławką pr, paste, awk, xargs, sedipure bash ! ( xargsjest wolniejszy, wolniejszy niż bash !)
F. Hauri

Odpowiedzi:

182

awk:

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

uwaga, na końcu wyjścia znajduje się pusty wiersz.

sed:

sed 'N;s/\n/ /' yourFile
Kent
źródło
Nie działa z kolorowym wydrukiem. Próbowałem wszystkiego w tym pytaniu i odpowiedziach i nic nie działało, gdy wydruk jest w kolorze ansi. Testowane na Ubuntu 13.04
Leo Gallucci
1
@elgalu: Ponieważ kolory ANSI to tylko zbiór kombinacji znaków ucieczki. Wykonaj hexedit na takim wyjściu, aby zobaczyć, co masz.
not2qubit
7
To rozwiązanie awk może się zepsuć, jeśli zostaną w nim znalezione printfłańcuchy rozwinięcia, takie jak . Tego niepowodzenia można uniknąć w następujący sposób:%s$0'NR%2{printf "%s ",$0;next;}1'
ghoti
9
Ponieważ naprawdę trudno jest google, co oznacza 1po nawiasie zamykającym?
erikbwork
5
@ erikb85 Proszę bardzo, stackoverflow.com/questions/24643240/…
Viraj
243

paste nadaje się do tej pracy:

paste -d " "  - - < filename
glenn jackman
źródło
10
Myślę, że jest to najlepsze przedstawione rozwiązanie, mimo że nie używałem ani seda, ani awk. Na wejściu, który jest nieparzystą liczbą wierszy, rozwiązanie awk Kenta pomija ostatnią nową linię, jego rozwiązanie sed pomija ostatnią linię w jego trzydziestce, a moje rozwiązanie powtarza ostatnią linię. pastez drugiej strony zachowuje się doskonale. +1.
ghoti
8
Często używam, cutale zawsze o nich zapominam paste. Świetnie nadaje się do tego problemu. Musiałem połączyć wszystkie linie ze stdin i zrobiłem to z łatwością paste -sd ' ' -.
Clint Pachl
4
Proste i piękne!
krlmlr
8
więc -wredne stdin, więc paste - -średnie czytanie ze standardowego wejścia, a następnie czytanie ze standardowego wejścia, możesz ułożyć tyle z nich, ile chcesz, czego oczekuję.
ThorSummoner,
1
Tak, @ThorSummoner ... Musiałem wkleić każde trzy wiersze w jeden wiersz i wkleić - - - i działało idealnie.
Daniel Goldfarb
35

Alternatywa dla sed, awk, grep:

xargs -n2 -d'\n'

Jest to najlepsze, gdy chcesz połączyć N wierszy i potrzebujesz tylko wyjścia rozdzielanego spacjami.

Moja pierwotna odpowiedź była xargs -n2taka, że ​​oddziela się raczej słowami niż liniami. -dmożna użyć do podzielenia wejścia dowolnym pojedynczym znakiem.

nnog
źródło
4
To fajna metoda, ale działa na słowach, a nie liniach. Aby działało na liniach, można dodać-d '\n'
Don Hatch
2
Wow, jestem zwykłym xargsużytkownikiem, ale nie wiedziałem o tym. Świetna wskazówka.
Sridhar Sarnobat
1
Uwielbiam to. Tak czysty.
Alexander Guo
28

Jest więcej sposobów na zabicie psa niż powieszenie. [1]

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

W cudzysłowie umieść dowolny separator.


Bibliografia:

  1. Pierwotnie „Wiele sposobów na oskórowanie kota”, powrócił do starszego, potencjalnie pierwotnego wyrażenia, które również nie ma nic wspólnego ze zwierzętami.
ghoti
źródło
Uwielbiam to rozwiązanie.
luis.espinal
5
Jako właścicielka kota nie doceniam tego rodzaju humoru.
witkacy26
4
@ witkacy26, Wyrażenie dostosowane do Twojego problemu.
ghoti
Uwielbiam to rozwiązanie awk, ale nie rozumiem, jak to działa: S
Rubendob,
@Rubendob - awk czyta każdy wiersz wejścia i umieszcza go w zmiennej $0. getlineKomenda chwyta również „następny” linii wejścia i umieszcza go w $0. Tak więc pierwsza instrukcja przechwytuje pierwszą linię, a polecenie print łączy to, co zostało zapisane w zmiennej, keyz łańcuchem zawierającym przecinek, wraz z wierszem, który został pobrany za pomocą getline. Jaśniej? :)
ghoti
12

Oto moje rozwiązanie w bash:

while read line1; do read line2; echo "$line1, $line2"; done < data.txt
Hai Vu
źródło
11

Chociaż wydaje się, że poprzednie rozwiązania zadziałałyby, gdyby w dokumencie wystąpiła pojedyncza anomalia, wynik zostałby rozpadnięty. Poniżej jest trochę bezpieczniej.

sed -n '/KEY/{
N
s/\n/ /p
}' somefile.txt
JD
źródło
3
Dlaczego to jest bezpieczniejsze? Co robi /KEY/? Co pna koniec?
Stewart
gdy /KEY/wyszukuje linii z KEY. gdy pdrukuje wynik na zewnątrz. jest bezpieczniejsze, ponieważ stosuje operację tylko na liniach z KEYw nim.
minghua,
11

Oto inny sposób 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

Jak wskazał Ed Morton w komentarzach, lepiej jest dodać szelki dla bezpieczeństwa i pareny dla przenośności.

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

ORSoznacza Output Record Separator. To, co tutaj robimy, to testowanie warunku przy użyciu funkcji NRprzechowującej numer wiersza. Jeśli modulo of NRjest wartością true (> 0), to ustawiamy Separator pola wyjściowego na wartość FS(Separator pola), którym domyślnie jest spacja, w przeciwnym razie przypisujemy wartośćRS (Separator rekordów), czyli znak nowej linii.

Jeśli chcesz dodać ,jako separator, użyj następującego:

awk '{ ORS = (NR%2 ? "," : RS) } 1' file
jaypal singh
źródło
1
Zdecydowanie właściwe podejście, więc +1, ale zastanawiam się, jaki warunek jest oceniany, aby wywołać domyślną akcję drukowania rekordu. Czy to dlatego, że zadanie się powiodło? Czy jest to proste ORSi jest to traktowane tak, jak trueskoro ORS pobiera wartość, która nie jest zerem lub ciągiem zerowym, i awks zgaduje, że powinno to być żądło zamiast porównania liczbowego? Czy to coś innego? Naprawdę nie jestem pewien, więc napisałbym to jako awk '{ORS=(NR%2?FS:RS)}1' file. Umieściłem w nawiasach trójskładnikowe wyrażenie, aby zapewnić przenośność.
Ed Morton,
1
@EdMorton Tak, właśnie widziałem kilka pozytywnych opinii na temat tej odpowiedzi, która miała ją zaktualizować, aby uwzględnić szelki dla bezpieczeństwa. Doda również parens.
jaypal singh
7

"ex" to edytor liniowy z możliwością obsługi skryptów, należący do tej samej rodziny co sed, awk, grep itp. Myślę, że może to być to, czego szukasz. Wiele nowoczesnych klonów / następców vi ma również tryb vi.

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

Ten mówi, dla każdej linii, jeśli pasuje „KEY” wykonać j OIN następnego wiersza. Po wykonująca polecenia (wobec wszystkich liniach), wydać w obrządku i q UIT.

Justin
źródło
4

Jeśli Perl jest opcją, możesz spróbować:

perl -0pe 's/(.*)\n(.*)\n/$1 $2\n/g' file.txt
andrefs
źródło
Czy -0każe perlowi ustawić separator rekordów ( $/)na null, abyśmy mogli objąć wiele linii w naszym dopasowanym wzorcu. Strony podręcznika są zbyt techniczne, abym mógł zrozumieć, co to oznacza w praktyce.
Sridhar Sarnobat
4

Możesz użyć awk w ten sposób, aby połączyć dwie pary linii:

awk '{ if (NR%2 != 0) line=$0; else {printf("%s %s\n", line, $0); line="";} } \
     END {if (length(line)) print line;}' flle
anubhava
źródło
4

Inne rozwiązania wykorzystujące vim (tylko w celach informacyjnych).

Rozwiązanie 1 :

Otwórz plik w vimie vim filename, a następnie wykonaj polecenie:% normal Jj

To polecenie jest bardzo łatwe do zrozumienia:

  • %: dla wszystkich linii,
  • normal: wykonuje normalne polecenie
  • Jj: wykonaj polecenie Join, a następnie przejdź do poniższej linii

Następnie zapisz plik i wyjdź za pomocą :wq

Rozwiązanie 2 :

Wykonaj polecenie w powłoce, vim -c ":% normal Jj" filenamea następnie zapisz plik i zakończ za pomocą :wq.

Jensen
źródło
Również norm!bardziej wytrzymały niż normalw przypadku, gdy Jzostał przemapowany. +1 za rozwiązanie vim.
qeatzy
@qeatzy Dziękuję, że mnie tego nauczyłeś. Bardzo się cieszę, że to wiem. ^ _ ^
Jensen
3

Możesz także użyć następującego polecenia vi:

:%g/.*/j
Jdamian
źródło
Lub nawet :%g//jdlatego, że wszystko, czego potrzebujesz, to dopasowanie do wykonania złączenia , a łańcuch pusty jest nadal prawidłowym wyrażeniem regularnym.
ghoti
1
@ghoti, W Vimie, gdy używasz tylko //, zostanie użyty poprzedni wzorzec wyszukiwania. Jeśli nie ma poprzedniego wzorca, Vim po prostu zgłasza błąd i nic nie robi. Rozwiązanie Jdamiana działa cały czas.
Tzunghsing David Wong
1
@TzunghsingDavidWong - to dobry wskaźnik dla użytkowników vima. Na szczęście dla mnie ani pytanie, ani ta odpowiedź nie wspominały o vimie.
ghoti
3

Nieznaczne wahania na odpowiedź Glenn Jackmana za pomocą paste: jeśli wartość dla -dopcji ogranicznika zawiera więcej niż jeden znak, pasteprzechodzi po znaków jeden po drugim, i połączono z -sopcji wciąż robi to podczas przetwarzania tego samego pliku wejściowego.

Oznacza to, że możemy użyć tego, co chcemy, jako separatora i sekwencji ucieczki \n aby połączyć dwie linie jednocześnie.

Za pomocą przecinka:

$ 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

i znak dolara:

$ 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

Czego to nie może zrobić, to użyć separatora składającego się z wielu znaków.

Dodatkowo, jeśli pastejest zgodny z POSIX, nie zmieni to nowej linii ostatniej linii w pliku, więc dla pliku wejściowego z nieparzystą liczbą linii, jak

KEY 4048:1736 string
3
KEY 0:1772 string

paste nie przyczepi się do znaku separacji w ostatniej linii:

$ paste -s -d ',\n' infile
KEY 4048:1736 string,3
KEY 0:1772 string
Benjamin W.
źródło
1
nawk '$0 ~ /string$/ {printf "%s ",$0; getline; printf "%s\n", $0}' filename

To brzmi jak

$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
Shahab Khan
źródło
1

W przypadku, gdy musiałem połączyć dwie linie (dla łatwiejszego przetwarzania), ale zezwolić na dane poza specyfikacją, uznałem to za przydatne

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

wyjście wygląda wtedy następująco:

Convert_data.txt

string1=x string2=y
string3
string4
Ben Taylor
źródło
1

Innym podejściem używającym vim byłoby:

:g/KEY/join

Odnosi to join(do wiersza poniżej) do wszystkich wierszy, które zawierają to słowo KEY. Wynik:

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
David542
źródło
0

Najprostszy sposób jest tutaj:

  1. Usuń parzyste linie i zapisz je w jakimś pliku tymczasowym 1.
  2. Usuń nieparzyste linie i zapisz je w jakimś pliku tymczasowym 2.
  3. Połącz dwa pliki w jeden, używając polecenia wklejania z -d (oznacza usunięcie spacji)

sed '0~2d' file > 1 && sed '1~2d' file > 2 && paste -d " " 1 2
Serg
źródło
0
perl -0pE 's{^KEY.*?\K\s+(\d+)$}{ $1}msg;' data.txt > data_merged-lines.txt

-0zjada cały plik zamiast czytać go wiersz po wierszu;
pEotacza kod pętlą i drukuje dane wyjściowe, zobacz szczegóły w http://perldoc.perl.org/perlrun.html ;
^KEYdopasuj „KEY” na początku wiersza, po którym następuje niechciane dopasowanie czegokolwiek ( .*?) przed sekwencją

  1. jedna lub więcej spacji \s+dowolnego rodzaju, w tym znaki końca wiersza;
  2. jedną lub więcej cyfr, (\d+)które przechwytujemy i później ponownie wstawiamy jako $1;

po którym następuje koniec wiersza $ .

\Kwygodnie wyklucza wszystko po lewej stronie z podstawiania, więc { $1}zastępuje tylko 1-2 sekwencje, patrz http://perldoc.perl.org/perlre.html .

Onlyjob
źródło
0

Bardziej ogólne rozwiązanie (pozwala na połączenie więcej niż jednej kolejnej linii) jako skrypt powłoki. To dodaje linię między nimi, ponieważ potrzebowałem widoczności, ale można to łatwo naprawić. W tym przykładzie linia „klucz” kończy się na:, a żadne inne nie.

#!/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
Jan Parcel
źródło
-1

Wypróbuj następujący wiersz:

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

Umieść separator pomiędzy

"$line1 $line2";

np. jeśli separatorem jest |, to:

"$line1|$line2";
Suman
źródło
Ta odpowiedź nie polega na dodaniu niczego, czego nie ma w odpowiedzi Hai Vu, która została opublikowana 4 lata przed Twoją.
fedorqui 'SO przestań szkodzić'
Zgadzam się częściowo, staram się dodać wyjaśnienie i bardziej ogólne Nie będzie też edytować starego pliku. Dziękuję za sugestię
Suman,
-2

Możesz użyć w xargsten sposób:

xargs -a file
RSG
źródło
% cat> file abc% xargs -a file abc% Działa dla mnie
RSG
Robi coś, tak, ale nie to, o co prosił OP. W szczególności łączy jak najwięcej linii. Właściwie możesz dostać to, czego chcesz, xargs -n 2ale ta odpowiedź w ogóle tego nie wyjaśnia.
tripleee