Fusionnez deux fichiers ligne par ligne avec le délimiteur triple pipe symbol "|||"

14

J'ai deux fichiers parallèles avec le même nombre de lignes dans deux langues et je prévois de fusionner ces deux fichiers ligne par ligne avec le délimiteur |||. Par exemple, les deux fichiers sont les suivants:

Déposer un:

1Mo 1,1 I love you.
1Mo 1,2 I like you.
Hi 1,3 I am hungry.
Hi 1,4 I am foolish.

Fichier B:

1Mo 1,1 Ich liebe dich.
1Mo 1,2 Ich mag dich.
Hi 1,3 Ich habe Durst.
Hi 1,4 Ich bin neu.

La sortie attendue est la suivante:

1Mo 1,1 I love you. ||| 1Mo 1,1 Ich liebe dich.
1Mo 1,2 I like you. ||| 1Mo 1,2 Ich mag dich.
Hi 1,3 I am hungry. ||| Hi 1,3 Ich habe Durst.
Hi 1,4 I am foolish. ||| Hi 1,4 Ich bin neu.

J'ai essayé la pastecommande telle que:

paste -d "|||" fileA fileB

Mais la sortie renvoyée ne contient qu'un seul tube tel que:

1Mo 1,1 I love you. |1Mo 1,1 Ich liebe dich.
1Mo 1,2 I like you. |1Mo 1,2 Ich mag dich.

Existe-t-il un moyen de séparer chaque paire de lignes par des tripes |||?

Froncer les sourcils
la source
8
paste -d '|||' fileA - - fileB < /dev/null
Stéphane Chazelas
5
offtopic, mais vos traductions ne sont pas correctes;) "Ich habe Durst" = je suis thisrty, "Ich bin neu" = je suis nouveau ... ne signifie pas nécessairement que vous êtes stupide. ... juste au cas où vous apprenez réellement l'allemand ...
dave_alcarin
@ StéphaneChazelas Thx, mais ma sortie ne contient toujours qu'un seul tube ...
Frown
@dave_alcarin Dank sehr!
Frown

Réponses:

20

Avec la pâte POSIX :

:|paste -d ' ||| ' fileA - - - - fileB

pasteconcaténera les lignes correspondantes de tous les fichiers d'entrée. Ici, nous avons six fichiers, fileAquatre fichiers fictifs de la norme dans -et fileB.

La liste des délimiteurs comprend un espace, trois tuyaux et un espace dans cet ordre sera utilisé par pastecirculairement.

Pour la première ligne de six fichiers, fileAsera concaténé avec le premier fichier factice (qui n'est rien, grâce à la opérateur no-op :), produit line1-fileA<space>.

Le premier fichier factice sera concaténé avec le second par un tuyau, produire line1-fileA |, puis le deuxième fichier factice avec le troisième fichier factice, produireline1-fileA || , le troisième fichier factice avec le quatrième fichier factice, produire line1-fileA |||.

Et le quatrième fichier factice avec fileB , produire line1-fileA ||| line1-fileB.

Cette étape sera répétée pour toutes les lignes, vous donnera le résultat attendu.


L'utilisation de :|est pour moins de frappe, et est principalement utilisée dans le shell interactif. Dans un script, vous devez utiliser:

</dev/null paste -d ' ||| ' fileA - - - - fileB

pour empêcher la génération d'un sous-shell.

cuonglm
la source
1
+1 pour le :|. alternative intelligente à</dev/null
cas
4
... et +1 pour l'utilisation intelligente de 4 fichiers factices à partir de l'entrée standard avec - - - -, mais la prochaine fois, vous pouvez même écrire quelques lignes pour expliquer :)
Hastur
Thx, mais j'obtiens toujours la sortie avec un tuyau ...
Frown
@hui, avez-vous exécuté la commande exactement comme indiqué, y compris tous les tirets et les espaces? Quel est votre système d'exploitation?
Stéphane Chazelas
:|paste -d '|' fileA - - fileBdonne la version la plus correcte sans le délimiteur d'espace.
Pål GD
7

Eh bien, cela n'utilise pas sed, awk ou grep, mais vous pouvez le faire assez facilement en bash. La commande est:

(while IFS= read -r a <&3 && IFS= read -r b <&4; do echo "$a ||| $b"; done) 3<fileA 4<fileB

Le problème avec la pâte est que le délimiteur est un seul caractère. Vous pouvez également insérer un seul caractère et utiliser sed pour le transformer, mais cela pourrait être source d'erreurs si le caractère apparaît déjà dans le fichier d'entrée.

user3188445
la source
2
Votre solution ne fonctionnera pas si la ligne contient un caractère barre oblique inverse ou commence par un tiret. Vous souhaitez utiliser IFS=avant chacun read. Vous pouvez facilement le faire avec paste. Voir ma réponse , et aussi celle-ci pour voir pourquoi devrait éviter d'utiliser la whileboucle dans le script shell.
cuonglm
Cela fonctionne pour mon dossier. Beaucoup Thx !!!
Frown
5

Une version awk (GNU)

awk '{printf ("%s ||| ", $0); getline < "fileB"; print $0 }' fileA

Avec la getlinecommande in awk, vous pouvez définir $0(toutes les variables pour les colonnes) à partir du prochain enregistrement d'entrée, si getline < "filename"vous définissez le suivant à $0partir du fichier spécifié.

getline <"fichier" Fixe $ 0 à partir du prochain enregistrement de fichier; définir NF.


Pourquoi votre tentative n'a pas fonctionné comme prévu? De man pastenous pouvons lire

-d, --delimiters=LIST
     reuse characters from LIST instead of TABs

mais il utilise les délimiteurs un pour chaque colonne .

Donc, la commande
paste -d '|*|*' fileA fileB fileA fileBme donne des lignes comme

Hi 1,3 I am hungry.|Hi 1,3 Ich habe Durst.*Hi 1,3 I am hungry.|Hi 1,3 Ich...
Hi 1,4 I am foolish.|Hi 1,4 Ich bin neu.*Hi 1,4 I am foolish.|Hi 1,4 Ich...


Une sedsolution que je suggère d'éviter même si elle est proche de votre tentative d'origine, car elle corrige le comportement obtenu à votre objectif d'origine:

 paste -d '|' fileA fileB | sed 's/|/|||/g'

À éviter car vous remplacez chaque modèle |par le nouveau |||, mais vous devez supposer que le symbole de tuyau ( |) n'est pas présent dans vos données , sinon vous devez traiter des cas spéciaux et rendre le code plus complexe pour éviter les effets secondaires.


Une variante avec la construction Here String [ 1 ]<<<

 paste -d ' ||| ' fileA - - - - fileB  <<< ''

Vous définissez 5 délimiteurs avec -d ' ||| '(espace, |, |, |, espace) et 4 fichiers factices ( - - - -) qui prendront les données de la chaîne vide ''.


Testé sur GNU Awk 4.0.1, paste (GNU coreutils) 8.21 et sed (GNU sed) 4.2.2

Hastur
la source
Thx, la commande awk fonctionne!
Frown
1
De rien. Mise à jour de la réponse en ajoutant un sedexemple pour éviter (:-)) et plus de commentaires.
Hastur
4

Si vous voulez éviter la magie et le drame des délimiteurs circulaires et des fichiers factices, vous pouvez simplement ajouter votre délimiteur à un fichier avant de les coller:

paste <(sed 's/$/ |||/' filea) fileb

donne

1Mo 1,1 I love you. ||| 1Mo 1,1 Ich liebe dich.
1Mo 1,2 I like you. ||| 1Mo 1,2 Ich mag dich.
Hi 1,3 I am hungry. ||| Hi 1,3 Ich habe Durst.
Hi 1,4 I am foolish. |||    Hi 1,4 Ich bin neu.
snth
la source
J'aime ça pour la simplicité. Je pense que vous voulez dire "pré-ajouter", pas "ajouter" cependant. Consultez la réponse awk de Hastur pour la version awk de cela.
Wildcard
Vous devez remplacer la substitution de processus par un tube, de sorte que vous n'aurez pas la limite du nombre de shells la prenant en charge.
cuonglm
@Wildcard oui, préfixez, mais je vais le réécrire pour l'ajouter à filea. Je pense que awk est un peu exagéré pour cela.
snth
@cuonglm true, mais je voulais éviter les tuyaux pour plus de clarté. Je sentais qu'une pipe ferait ressembler aux fichiers factices, mais vous avez raison
snth
0

vous pouvez aussi le faire en python de cette façon.

lines1 = [ line.rstrip() for line in open("file1") ]
lines2 = [ line.rstrip() for line in open("file2") ]
for i in xrange((len(lines1))): print lines1[i] + " ||| " + lines2[i]
... 
1Mo 1,1 I love you. ||| 1Mo 1,1 Ich liebe dich.
1Mo 1,2 I like you. ||| 1Mo 1,2 Ich mag dich.
Hi 1,3 I am hungry. ||| Hi 1,3 Ich habe Durst.
Hi 1,4 I am foolish. ||| Hi 1,4 Ich bin neu.
c4f4t0r
la source