Comment fusionner toutes les deux lignes en une seule à partir de la ligne de commande?

151

J'ai un fichier texte au format suivant. La première ligne est la «CLÉ» et la deuxième ligne est la «VALEUR».

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

J'ai besoin de la valeur dans la même ligne que celle de la clé. La sortie devrait donc ressembler à ceci ...

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

Ce sera mieux si je pouvais utiliser un délimiteur comme $ou ,:

KEY 4048:1736 string , 3

Comment fusionner deux lignes en une seule?

shantanuo
la source
Il y a de nombreuses façons de faire cela! Je l' ai fait un petit banc avec pr, paste, awk, xargs, sedetpure bash ! ( xargsest le plus lent, le plus lent que bash !)
F.Hauri

Réponses:

183

awk:

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

Remarque, il y a une ligne vide à la fin de la sortie.

sed:

sed 'N;s/\n/ /' yourFile
Kent
la source
Ne fonctionne pas avec une sortie couleur. J'ai tout essayé sur ce Q&A et rien n'a fonctionné lorsque la sortie est colorée. Testé sur Ubuntu 13.04
Leo Gallucci
1
@elgalu: Parce que les couleurs ANSI ne sont qu'un tas de combinaisons de caractères d'échappement. Faites un hexedit sur une telle sortie, pour voir ce que vous avez.
not2qubit
7
Cette solution awk peut être interrompue si printfdes chaînes d'expansion comme %ssont trouvées à l'intérieur $0. Cet échec peut être évité comme ceci:'NR%2{printf "%s ",$0;next;}1'
ghoti
9
Parce qu'il est vraiment difficile de rechercher sur Google, que signifie le 1signe après l'accolade de fermeture?
erikbwork
5
@ erikb85 Ici vous allez stackoverflow.com/questions/24643240/…
Viraj
243

paste est bon pour ce travail:

paste -d " "  - - < filename
Glenn Jackman
la source
10
Je pense que c'est la meilleure solution présentée, malgré l'utilisation de ni sed ni awk. Sur une entrée qui est un nombre impair de lignes, la solution awk de Kent saute la dernière ligne, sa solution sed saute la dernière ligne dans son entirty, et ma solution répète la dernière ligne. paste, d'autre part, se comporte parfaitement. +1.
ghoti
8
J'utilise souvent cutmais j'oublie toujours paste. Il rock pour ce problème. J'avais besoin de combiner toutes les lignes de stdin et je l'ai fait facilement avec paste -sd ' ' -.
Clint Pachl
4
Simple et beau!
krlmlr
8
donc -signifie stdin, donc paste - -signifie lire à partir de stdin, puis lire à partir de stdin, vous pouvez en empiler autant que vous le souhaitez.
ThorSummoner
1
Oui, @ThorSummoner ... J'ai dû coller toutes les trois lignes en une seule ligne et j'ai collé - - - et cela a parfaitement fonctionné.
Daniel Goldfarb
35

Alternative à sed, awk, grep:

xargs -n2 -d'\n'

Ceci est préférable lorsque vous souhaitez joindre N lignes et que vous n'avez besoin que d'une sortie délimitée par des espaces.

Ma réponse originale était celle xargs -n2qui sépare les mots plutôt que les lignes. -dpeut être utilisé pour diviser l'entrée par n'importe quel caractère unique.

nnog
la source
4
C'est une bonne méthode, mais elle fonctionne sur des mots, pas sur des lignes. Pour le faire fonctionner en ligne, pourrait ajouter-d '\n'
Don Hatch
2
Wow, je suis un xargsutilisateur régulier mais je ne le savais pas. Bon conseil.
Sridhar Sarnobat
1
J'aime cela. Si propre.
Alexander Guo
28

Il y a plus de façons de tuer un chien que de le suspendre. [1]

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

Mettez le délimiteur de votre choix entre les guillemets.


Références:

  1. À l'origine, «De nombreuses façons d'écorcher le chat», est revenu à une expression plus ancienne et potentiellement originaire qui n'a rien à voir avec les animaux de compagnie.
Ghoti
la source
J'adore cette solution.
luis.espinal
5
En tant que propriétaire de chat, je n'apprécie pas ce genre d'humour.
witkacy26
4
@ witkacy26, Expression ajustée selon votre préoccupation.
ghoti
J'adore cette solution awk mais je ne comprends pas comment cela fonctionne: S
Rubendob
@Rubendob - awk lit chaque ligne d'entrée et la place dans la variable $0. La getlinecommande saisit également "la prochaine" ligne d'entrée et la place dans $0. Ainsi, la première instruction saisit la première ligne et la commande d'impression concatène ce qui a été sauvegardé dans la variable keyavec une chaîne contenant une virgule, ainsi que la ligne qui a été récupérée à l'aide de getline. Plus clair? :)
ghoti
12

Voici ma solution en bash:

while read line1; do read line2; echo "$line1, $line2"; done < data.txt
Hai Vu
la source
11

Bien qu'il semble que les solutions précédentes fonctionnent, si une seule anomalie se produit dans le document, la sortie se décompose. Ci-dessous est un peu plus sûr.

sed -n '/KEY/{
N
s/\n/ /p
}' somefile.txt
JD
la source
3
Pourquoi est-ce plus sûr? Que fait /KEY/-on? Que fait le pà la fin?
Stewart
la /KEY/recherche de la ligne avec le KEY. le pimprime le résultat. c'est plus sûr car il n'applique l'opération que sur les lignes contenant un KEY.
minghua
11

Voici une autre façon avec 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

Comme indiqué par Ed Morton dans les commentaires, il est préférable d'ajouter des accolades pour la sécurité et des parens pour la portabilité.

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

ORSsignifie Output Record Separator. Ce que nous faisons ici, c'est tester une condition en utilisant le NRqui stocke le numéro de ligne. Si le modulo de NRest une valeur vraie (> 0), nous définissons le séparateur de champ de sortie sur la valeur de FS(séparateur de champ) qui par défaut est un espace, sinon nous attribuons la valeur de RS(séparateur d'enregistrement) qui est une nouvelle ligne.

Si vous souhaitez ajouter ,comme séparateur, utilisez ce qui suit:

awk '{ ORS = (NR%2 ? "," : RS) } 1' file
jaypal singh
la source
1
Certainement la bonne approche donc +1, mais je me demande quelle est la condition qui est évaluée pour invoquer l'action par défaut d'impression de l'enregistrement. Est-ce que la mission a réussi? Est-ce simplement ORSet cela est traité comme étant truedonné que ORS obtient une valeur qui n'est pas zéro ou une chaîne nulle et qui devine correctement que cela devrait être une piqûre au lieu d'une comparaison numérique? Est-ce autre chose? Je ne suis vraiment pas sûr et je l'aurais donc écrit comme awk '{ORS=(NR%2?FS:RS)}1' file. J'ai mis entre parenthèses l'expression ternaire pour assurer la portabilité également.
Ed Morton
1
@EdMorton Oui, je viens de voir quelques votes positifs sur cette réponse sur le point de la mettre à jour pour inclure les accolades pour la sécurité. Ajoutera également des parens.
jaypal singh
7

"ex" est un éditeur de ligne scriptable qui appartient à la même famille que sed, awk, grep, etc. Je pense que c'est peut-être ce que vous recherchez. De nombreux clones / successeurs vi modernes ont également un mode vi.

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

Cela dit , pour chaque ligne, si elle correspond à « KEY » effectuer un j oin de la ligne suivante. Une fois cette commande terminée (sur toutes les lignes), lancez un w rite et q uit.

Justin
la source
4

Si Perl est une option, vous pouvez essayer:

perl -0pe 's/(.*)\n(.*)\n/$1 $2\n/g' file.txt
andrefs
la source
Est-ce que le -0perl dit de définir le séparateur d'enregistrement ( $/)à null, afin que nous puissions étendre plusieurs lignes dans notre modèle de correspondance. Les pages de manuel sont un peu trop techniques pour que je puisse comprendre ce que cela signifie en pratique.
Sridhar Sarnobat
4

Vous pouvez utiliser awk comme ceci pour combiner deux paires de lignes:

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

Une autre solution utilisant vim (juste pour référence).

Solution 1 :

Ouvrez le fichier dans vim vim filename, puis exécutez la commande:% normal Jj

Cette commande est très simple à comprendre:

  • %: pour toutes les lignes,
  • normal: exécuter la commande normale
  • Jj: exécutez la commande Join, puis passez à la ligne ci-dessous

Après cela, enregistrez le fichier et quittez avec :wq

Solution 2 :

Exécutez la commande dans le shell, vim -c ":% normal Jj" filenamepuis enregistrez le fichier et quittez avec :wq.

Jensen
la source
Aussi norm!plus robuste qu'en normalcas de Jremappage. +1 pour la solution vim.
qeatzy
@qeatzy Merci de m'avoir appris cela. Très content de le savoir. ^ _ ^
Jensen
3

Vous pouvez également utiliser la commande vi suivante:

:%g/.*/j
Jdamian
la source
Ou même :%g//jpuisque tout ce dont vous avez besoin est une correspondance pour que la jointure soit exécutée, et une chaîne nulle est toujours une expression régulière valide.
ghoti
1
@ghoti, Dans Vim, lorsque vous utilisez juste //, le modèle de recherche précédent sera utilisé à la place. S'il n'y a pas de modèle précédent, Vim signale simplement une erreur et ne fait rien. La solution de Jdamian fonctionne tout le temps.
Tzunghsing David Wong
1
@TzunghsingDavidWong - c'est un bon pointeur pour les utilisateurs de vim. Handily pour moi, ni la question ni cette réponse ne mentionnaient vim.
ghoti le
3

Une légère variation de la réponse de Glenn Jackman en utilisant paste: si la valeur de l' -doption délimiteur contient plus d'un caractère, fait pastedéfiler les caractères un par un et combiné avec les -soptions continue de le faire tout en traitant le même fichier d'entrée.

Cela signifie que nous pouvons utiliser tout ce que nous voulons comme séparateur plus la séquence d'échappement \npour fusionner deux lignes à la fois.

En utilisant une virgule:

$ 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

et le signe dollar:

$ 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

Ce que cela ne peut pas faire, c'est utiliser un séparateur composé de plusieurs caractères.

En prime, si le pasteest compatible POSIX, cela ne modifiera pas la nouvelle ligne de la dernière ligne du fichier, donc pour un fichier d'entrée avec un nombre impair de lignes comme

KEY 4048:1736 string
3
KEY 0:1772 string

paste ne clouera pas sur le caractère de séparation sur la dernière ligne:

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

Cela se lit comme

$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
la source
1

Dans le cas où je devais combiner deux lignes (pour un traitement plus facile), mais laisser les données au-delà du spécifique, j'ai trouvé cela 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

la sortie ressemble alors à:

converti_data.txt

string1=x string2=y
string3
string4
Ben Taylor
la source
1

Une autre approche utilisant vim serait:

:g/KEY/join

Cela applique un join(à la ligne en dessous) à toutes les lignes qui contiennent le mot KEY. Résultat:

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
la source
0

Le moyen le plus simple est ici:

  1. Supprimez les lignes paires et écrivez-les dans un fichier temporaire 1.
  2. Supprimez les lignes impaires et écrivez-les dans un fichier temporaire 2.
  3. Combinez deux fichiers en un en utilisant la commande coller avec -d (signifie supprimer un espace)

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

-0engloutit le fichier entier au lieu de le lire ligne par ligne;
pEencapsule le code avec une boucle et imprime la sortie, voir les détails dans http://perldoc.perl.org/perlrun.html ;
^KEYcorrespond à "KEY" au début de la ligne, suivi d'une correspondance non gourmande de tout ( .*?) avant la séquence de

  1. un ou plusieurs espaces \s+de toute nature, y compris les sauts de ligne;
  2. un ou plusieurs chiffres (\d+)que nous capturons et réinsérons plus tard comme $1;

suivi de la fin de la ligne $.

\Kexclut commodément tout ce qui se trouve sur son côté gauche de la substitution, donc { $1}remplace seulement 1-2 séquence, voir http://perldoc.perl.org/perlre.html .

Onlyjob
la source
0

Une solution plus générale (permet de joindre plus d'une ligne de suivi) sous forme de script shell. Cela ajoute une ligne entre chacun, car j'avais besoin de visibilité, mais cela est facilement corrigé. Cet exemple est l'endroit où la ligne «clé» s'est terminée par: et aucune autre ligne ne l'a fait.

#!/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
Colis Jan
la source
-1

Essayez la ligne suivante:

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

Mettre un délimiteur entre

"$line1 $line2";

par exemple, si le délimiteur est |, alors:

"$line1|$line2";
Suman
la source
Cette réponse n'ajoute rien de non fourni dans la réponse de Hai Vu qui a été publiée 4 ans avant la vôtre.
fedorqui 'SO arrêtez de nuire'
Je suis d'accord partiellement, j'essaye d'ajouter des explications et plus génériques. Il n'éditera pas non plus l'ancien fichier. Merci pour votre suggestion
Suman
-2

Vous pouvez utiliser xargscomme ceci:

xargs -a file
RSG
la source
% cat> fichier abc% xargs -a fichier abc% Fonctionne pour moi
RSG
Il fait quelque chose, oui, mais pas ce que le PO a demandé. Plus précisément, il joint autant de lignes que possible. Vous pourriez en fait obtenir ce que vous voulez, xargs -n 2mais cette réponse n'explique pas du tout cela.
tripleee