Concaténer plusieurs fichiers avec le même en-tête

26

J'ai plusieurs fichiers avec le même en-tête et différents vecteurs en dessous. J'ai besoin de les concaténer tous, mais je veux que l'en-tête du premier fichier soit concaténé et je ne veux pas que les autres en-têtes soient concaténés car ils sont tous les mêmes.

par exemple: file1.txt

<header>INFO=<ID=DP,Number=1,Type=Integer>
<header>INFO=<ID=DP4,Number=4,Type=Integer>
A
B 
C

file2.txt

<header>INFO=<ID=DP,Number=1,Type=Integer>
<header>INFO=<ID=DP4,Number=4,Type=Integer>
D
E 
F

J'ai besoin que la sortie soit

<header>INFO=<ID=DP,Number=1,Type=Integer>
<header>INFO=<ID=DP4,Number=4,Type=Integer>
A
B
C
D
E 
F

Je pourrais écrire un script en R mais j'en ai besoin en shell?

Jana
la source

Réponses:

17

Si vous savez comment le faire en R, alors faites-le certainement en R. Avec les outils Unix classiques, cela se fait le plus naturellement dans awk.

awk '
    FNR==1 && NR!=1 { while (/^<header>/) getline; }
    1 {print}
' file*.txt >all.txt

La première ligne du script awk correspond à la première ligne d'un fichier ( FNR==1) sauf si c'est également la première ligne de tous les fichiers ( NR==1). Lorsque ces conditions sont remplies, l'expression while (/^<header>/) getline;est exécutée, ce qui oblige awk à lire une autre ligne (en sautant la ligne actuelle) tant que la ligne actuelle correspond à l'expression régulière ^<header>. La deuxième ligne du script awk imprime tout sauf les lignes qui ont été précédemment ignorées.

Gilles 'SO- arrête d'être méchant'
la source
Merci Gilles. Chacun de mes fichiers est en Go. R ne sera pas efficace pour ce faire. C'est pourquoi j'ai demandé.
Jana
@Jana Y a-t-il des lignes qui ressemblent à des en-têtes mais qui ne sont pas en haut du fichier? Sinon, le moyen le plus rapide est d'utiliser grep(comme dans la réponse de Spoutnik ).
Gilles 'SO- arrête d'être méchant'
Non, les lignes d'en-tête sont similaires à tous les fichiers et elles sont juste en haut de chaque fichier. Ouais grep était plus rapide. Merci à vous deux
Jana
1
@Jana Soit dit en passant, si tous vos fichiers ont le même nombre de lignes d'en-tête, voici une autre façon (qui devrait être encore plus rapide): head -n 10 file1.txt >output.txt && tail -q -n +11 file*.txt >>output.txt(si vous avez 10 lignes d'en-tête). De plus, si vos fichiers ont des numéros dans leurs noms, méfiez-vous qui file9.txtest trié entre file89.txtet file90.txt. Si vos fichiers ont de tels chiffres file001.txt, ..., files009.txt, files010.txt, ..., puis files*.txtleur liste dans l'ordre.
Gilles 'SO- arrête d'être méchant'
Une meilleure solution (de stackoverflow.com/a/16890695/310441 ) qui ne nécessite pas de correspondance d'expressions régulières: awk 'FNR==1 && NR!=1{next;}{print}' *.csv
Owen
42

Une autre solution, similaire à " cat+grep" ci-dessus, utilisant tailet head:

  1. Écrivez l'en-tête du premier fichier dans la sortie:

    head -2 file1.txt > all.txt

    - head -2obtient les 2 premières lignes du fichier.

  2. Ajoutez le contenu de tous les fichiers:

    tail -n +3 -q file*.txt >> all.txt

    - -n +3crée taildes lignes d'impression du 3ème à la fin, lui -qdit de ne pas imprimer l'en-tête avec le nom du fichier (lu man), >>ajoute au fichier, ne le remplace pas par >.

Et bien sûr, vous pouvez mettre les deux commandes sur une seule ligne:

head -2 file1.txt > all.txt; tail -n +3 -q file*.txt >> all.txt

ou au lieu de les ;mettre &&entre eux pour vérifier le succès.

xealits
la source
3
Je suggère de continuer simplement à: (head -2 file1.txt ; tail -n +3 -q file*.txt ) > all.txtou(head -2 file1.txt && tail -n +3 -q file*.txt ) > all.txt
HongboZhu
4

Essayez de faire ceci:

$ cat file1.txt; grep -v "^<header" file2.txt
<header>INFO=<ID=DP,Number=1,Type=Integer>
<header>INFO=<ID=DP4,Number=4,Type=Integer>
A
B 
C
D
E 
F

REMARQUE

  • le -vdrapeau signifie inverser le match de
  • ^dans REGEX , signifie début de la chaîne
  • si vous avez un tas de fichiers, vous pouvez le faire

:

array=( files*.txt )
{ cat ${array[@]:0:1}; grep -v "^<header" ${array[@]:1}; } > new_file.txt

C'est une technique de découpage de tableau .

Gilles Quenot
la source
Merci sputnick, mais j'ai ~ 30 fichiers (file1.txt, file2.txt, file3.txt..filen.txt) à concaténer. Dois-je taper chaque nom de fichier ou existe-t-il d'autres façons de le faire?
Jana
Voir mon article édité avec la technique de découpage
Gilles Quenot
Cela supprime les <header>lignes n'importe où dans les fichiers, pas seulement au début. Cela peut ne pas être un problème ici, selon les données.
Gilles 'SO- arrête d'être méchant'
1
Plus simple:grep '^<header>' file1.txt >output.txt && grep -v '^<header>' file*.txt >>output.txt
Gilles 'SO- arrête d'être méchant'
@ Gilles: J'ai remarqué votre réponse après un long moment mais elle a été très utile
Jana
1

La tailcommande (sur GNU, au moins) a une option pour ignorer un nombre donné de lignes initiales. Pour imprimer à partir de la deuxième ligne, c'est-à-dire sauter un en-tête d'une ligne, procédez comme suit:tail -n+2 myfile

Donc, pour garder l'en-tête à deux lignes du premier fichier mais pas du second, dans Bash:

cat file1.txt <(tail -n+3 file2.txt) > combined.txt

Ou, pour de nombreux fichiers:

head -n1 file1.txt > combined.txt
for fname in *.txt
do
    tail -n+3 $fname >> combined.txt
done

Si une certaine chaîne est connue pour être présente dans toutes les lignes d'en-tête mais jamais dans le reste des fichiers d'entrée, grep -vc'est une approche plus simple, comme l'a montré sputnik.

etal
la source
1

Plus court (pas nécessairement plus rapide) avec sed:

sed -e '3,${/^<header>/d' -e '}' file*.txt > all.txt

Cela supprimera toutes les lignes <header>...commençant par la ligne 3, donc le premier en-tête est conservé et les autres en-têtes sont supprimés. S'il y a un nombre différent de lignes dans l'en-tête, ajustez la commande en conséquence (par exemple pour un en-tête à 6 lignes, utilisez 7plutôt que 3).
Si le nombre de lignes dans l'en-tête est inconnu, vous pouvez essayer comme ceci:

sed '1{
: again
n
/^<header>/b again
}
/^<header>/d
' file*.txt > all.txt
don_crissti
la source
0

array = (* .txt); head -1 $ {array [0]}> all.txt; tail -n +2 -q $ {array [@]: 0} >> all.txt

En supposant que vous utilisez un dossier avec des fichiers .txt avec le même en-tête qui doivent être combinés / concaténés, ce code combinerait tous les fichiers txt dans all.txt avec un seul en-tête. la première ligne (lignes séparées par des points-virgules) rassemble tous les fichiers texte à concaténer, les secondes lignes sortent l'en-tête du premier fichier txt dans all.txt et la dernière ligne concatène tous les fichiers texte rassemblés sans l'en-tête (en démarrant le concaténation à partir de la ligne 2) et l'ajoute à all.txt .

Eric
la source
une infime explication aiderait grandement les futurs utilisateurs
Jeff Schaller