Come unire ogni due righe in una dalla riga di comando?


151

Ho un file di testo con il seguente formato. La prima riga è "KEY" e la seconda riga è "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

Ho bisogno del valore nella stessa riga della chiave. Quindi l'output dovrebbe assomigliare a questo ...

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

Sarà meglio se potessi usare un delimitatore come $o ,:

KEY 4048:1736 string , 3

Come unire due righe in una?


C'è molto modo per farlo! Ho fatto un po 'di panchina con pr, paste, awk, xargs, sedepure bash ! ( xargsè il più lento, più lento del bash !)
F. Hauri,

Risposte:


183

awk:

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

nota, c'è una riga vuota alla fine dell'output.

sed:

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

Non funziona con output colorato. Ho provato di tutto in queste domande e risposte e nulla ha funzionato quando l'output è colorato. Testato su Ubuntu 13.04
Leo Gallucci il

1
@elgalu: Perché i colori ANSI sono solo un mucchio di combinazioni di caratteri di escape. Fai un hexedit su tale output, per vedere cosa hai.
not2qubit,

7
Questa soluzione awk può rompersi se all'interno si trovano printfstringhe di espansione come . Tale fallimento può essere evitato in questo modo:%s$0'NR%2{printf "%s ",$0;next;}1'
ghoti,

9
Perché è davvero difficile da google, cosa significa che 1dopo la parentesi graffa di chiusura?
erikbwork,


243

paste è buono per questo lavoro:

paste -d " "  - - < filename

10
Penso che questa sia la migliore soluzione presentata, nonostante non utilizzi né sed né awk. Su input che è un numero dispari di righe, la soluzione awk di Kent salta la nuova riga finale, la sua soluzione sed salta la riga finale nella sua interezza e la mia soluzione ripete l'ultima riga. pasted'altra parte, si comporta perfettamente. +1.
ghoti,

8
Uso spesso cutma mi dimentico sempre paste. Oscilla per questo problema. Avevo bisogno di combinare tutte le linee di stdin e l'ho fatto facilmente paste -sd ' ' -.
Clint Pachl,

4
Semplice e bella!
krlmlr,

8
quindi -significa stdin, quindi paste - -significa leggi da stdin, quindi leggi da stdin, puoi impilarne quanti ne desideri.
ThorSummoner l'

1
Sì, @ThorSummoner ... Ho dovuto incollare ogni tre righe in un'unica riga e ho incollato - - - e ha funzionato perfettamente.
Daniel Goldfarb,

35

Alternativa a sed, awk, grep:

xargs -n2 -d'\n'

Questo è il migliore quando vuoi unire N linee e hai bisogno solo di output delimitato da spazio.

La mia risposta originale era xargs -n2che separa le parole piuttosto che le righe. -dpuò essere usato per dividere l'input per ogni singolo carattere.


4
Questo è un bel metodo, ma funziona su parole, non su righe. Per farlo funzionare sulle linee, potrebbe aggiungere-d '\n'
Don Hatch,

2
Wow, sono un xargsutente normale ma non lo sapevo. Ottimo consiglio
Sridhar Sarnobat,

1
Amo questo. Così pulito.
Alexander Guo,

28

Ci sono più modi per uccidere un cane che impiccarsi. [1]

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

Inserisci qualsiasi delimitatore che ti piace tra virgolette.


Riferimenti:

  1. Originariamente "Un sacco di modi per scuoiare il gatto", è tornato a un'espressione più vecchia, potenzialmente originaria che non ha nulla a che fare con gli animali domestici.

Adoro questa soluzione.
luis.espinal,

5
Come proprietario di un gatto non apprezzo questo tipo di umorismo.
witkacy26,

4
@ witkacy26, espressione corretta in base alle tue preoccupazioni.
ghoti,

Adoro questa soluzione fantastica ma non capisco come funzioni: S
Rubendob,

@Rubendob - awk legge ogni riga di input e la inserisce nella variabile $0. Il getlinecomando prende anche la "prossima" riga di input e la inserisce $0. Quindi la prima istruzione prende la prima riga e il comando print concatena ciò che è stato salvato nella variabile keycon una stringa contenente una virgola, insieme alla riga che è stata recuperata usando getline. Più chiaro? :)
ghoti,

12

Ecco la mia soluzione in bash:

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

11

Anche se sembra che le soluzioni precedenti funzionerebbero, se si verifica una singola anomalia nel documento, l'output andrebbe in pezzi. Di seguito è un po 'più sicuro.

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

3
Perché è più sicuro? Cosa fa /KEY/? Cosa fa palla fine?
Stewart,

le /KEY/ricerche per la linea con il KEY. le pstampe il risultato fuori. è più sicuro perché applica solo l'operazione su linee con un KEYin.
minghua,

11

Ecco un altro modo con 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

Come indicato da Ed Morton nei commenti, è meglio aggiungere parentesi graffe per la sicurezza e parens per la portabilità.

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

ORSsta per Output Record Separator. Quello che stiamo facendo qui è testare una condizione usando quella NRche memorizza il numero di riga. Se il modulo di NRè un valore vero (> 0), impostiamo Output Separator sul valore di FS(Field Separator) che per impostazione predefinita è lo spazio, altrimenti assegniamo il valore di RS(Record Separator) che è newline.

Se si desidera aggiungere ,come separatore, utilizzare quanto segue:

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

1
Sicuramente l'approccio giusto quindi +1 ma mi chiedo quale sia la condizione che viene valutata per invocare l'azione predefinita di stampa del record. L'incarico è riuscito? È semplicemente ORSe viene trattato come truedato che ORS ottiene un valore diverso da zero o una stringa nulla e si sveglia indovinando correttamente che dovrebbe essere una puntura anziché un confronto numerico? È qualcos'altro? Non ne sono davvero sicuro e quindi l'avrei scritto come awk '{ORS=(NR%2?FS:RS)}1' file. Tra parentesi l'espressione ternaria per garantire anche la portabilità.
Ed Morton,

1
@EdMorton Sì, ho appena visto un paio di voti positivi su questa risposta che stava per aggiornarla per includere le parentesi graffe per la sicurezza. Aggiungerà anche le parentesi.
jaypal singh,

7

"ex" è un editor di linee di script che appartiene alla stessa famiglia di sed, awk, grep, ecc. Penso che potrebbe essere quello che stai cercando. Molti cloni / successori vi moderni hanno anche una modalità vi.

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

Questo dice che per ogni riga, se corrisponde a "KEY" eseguire una j oin della seguente riga. Dopo che completamento del comando (contro tutte le linee), rilascia un w rito e q uit.


4

Se Perl è un'opzione, puoi provare:

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

Fa il -0perl dicono di impostare il separatore di record ( $/)a null, in modo che possiamo occupare più righe nel nostro pattern matching Le pagine man sono un po 'troppo tecnico per me capire che cosa significa in pratica..
Sridhar Sarnobat

4

Puoi usare awk in questo modo per combinare sempre 2 coppie di linee:

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

4

Altre soluzioni che utilizzano vim (solo per riferimento).

Soluzione 1 :

Apri il file in vim vim filename, quindi esegui il comando:% normal Jj

Questo comando è molto facile da capire:

  • %: per tutte le linee,
  • normale: esegue il comando normale
  • Jj: esegui il comando Join, quindi passa alla riga inferiore

Successivamente, salva il file ed esci con :wq

Soluzione 2 :

Eseguire il comando nella shell, vim -c ":% normal Jj" filenamequindi salvare il file e uscire con :wq.


Anche norm!più robusto che normalnel caso sia Jstato rimappato. +1 per la soluzione vim.
qeatzy,

@qeatzy Grazie per avermelo insegnato. Molto contento di saperlo. ^ _ ^
Jensen,

3

Puoi anche usare il seguente comando vi:

:%g/.*/j

O anche :%g//jdal momento che tutto ciò che serve è una corrispondenza per l' esecuzione del join e una stringa nulla è ancora una regex valida.
ghoti,

1
@ghoti, In Vim, quando si utilizza just //, verrà utilizzato il modello di ricerca precedente. Se non esiste un modello precedente, Vim segnala semplicemente un errore e non fa nulla. La soluzione di Jdamian funziona sempre.
Tzunghsing David Wong,

1
@TzunghsingDavidWong - questo è un buon puntatore per gli utenti di VIM. Praticamente per me, né la domanda né questa risposta menzionavano vim.
ghoti,

3

Una leggera variazione sulla risposta di Glenn Jackman utilizzando paste: se il valore -ddell'opzione delimitatore contiene più di un carattere, pastescorre i caratteri uno alla volta e, combinato con le -sopzioni, continua a farlo mentre elabora lo stesso file di input.

Ciò significa che possiamo usare tutto ciò che vogliamo avere come separatore più la sequenza di escape \n per unire due linee alla volta.

Usando una virgola:

$ 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

e il simbolo del dollaro:

$ 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

Ciò che questo non può fare è utilizzare un separatore composto da più caratteri.

Come bonus, se pasteè conforme a POSIX, questo non modificherà la nuova riga dell'ultima riga nel file, quindi per un file di input con un numero dispari di righe come

KEY 4048:1736 string
3
KEY 0:1772 string

paste non attaccherà il carattere di separazione sull'ultima riga:

$ 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

Questo si legge come

$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

Nel caso in cui avessi bisogno di combinare due righe (per un'elaborazione più semplice), ma consenti ai dati oltre lo specifico, ho trovato utile

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

l'output quindi appare come:

converted_data.txt

string1=x string2=y
string3
string4

1

Un altro approccio che utilizza vim sarebbe:

:g/KEY/join

Questo applica un join(alla linea sotto di essa) a tutte le linee che contengono la parola KEY. Risultato:

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

Il modo più semplice è qui:

  1. Rimuovere le linee pari e scriverlo in alcuni file temporanei 1.
  2. Rimuovere le righe dispari e scriverlo in alcuni file temporanei 2.
  3. Combina due file in uno usando il comando incolla con -d (significa cancella spazio)

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

-0divora l'intero file invece di leggerlo riga per riga;
pEavvolge il codice con loop e stampa l'output, vedere i dettagli in http://perldoc.perl.org/perlrun.html ;
^KEYtrova "KEY" all'inizio della riga, seguito da una corrispondenza non avida di qualcosa ( .*?) prima della sequenza di

  1. uno o più spazi \s+di qualsiasi tipo, comprese le interruzioni di riga;
  2. una o più cifre (\d+)che catturiamo e successivamente reinseriamo come $1;

seguito dalla fine della linea $.

\Kesclude convenientemente tutto dalla parte sinistra dalla sostituzione, quindi { $1}sostituisce solo la sequenza 1-2, vedere http://perldoc.perl.org/perlre.html .


0

Una soluzione più generale (consente di unire più di una riga di follow-up) come script di shell. Questo aggiunge una linea tra ciascuno, perché avevo bisogno di visibilità, ma è facilmente rimediabile. Questo esempio è dove la linea "chiave" è finita: e nessun'altra linea ha fatto.

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

Prova la seguente riga:

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

Metti delimitatore nel mezzo

"$line1 $line2";

ad es. se il delimitatore è |, quindi:

"$line1|$line2";

Questa risposta non aggiunge nulla di non fornito nella risposta di Hai Vu pubblicata 4 anni prima della tua.
fedorqui "SO smettere di danneggiare" il

Sono parzialmente d'accordo, provo ad aggiungere una spiegazione più generica e non modificherà anche il vecchio file. Grazie per il tuo suggerimento
Suman

-2

Puoi usare xargscosì:

xargs -a file

% cat> file abc% xargs -a file abc% Funziona per me
RSG

Fa qualcosa, sì, ma non quello che l'OP ha richiesto. In particolare, unisce il maggior numero di righe possibile. In realtà potresti ottenere quello che vuoi xargs -n 2ma questa risposta non lo spiega affatto.
Tripleee,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.