J'ai un gros fichier input.dat qui ressemble à l'illustration ci-dessous.
kpoint1 : 0.0000 0.0000 0.0000
band No. band energies occupation
1 -52.8287 2.00000
2 -52.7981 2.00000
3 -52.7981 2.00000
kpoint2 : 0.0000 0.0000 0.0000
band No. band energies occupation
1 -52.8287 2.00000
2 -52.7981 2.00000
3 -52.7981 2.00000
J'ai besoin de diviser le fichier en 2 plus petits comme ci-dessous
kpoint1.dat
:
kpoint1 : 0.0000 0.0000 0.0000
band No. band energies occupation
1 -52.8287 2.00000
2 -52.7981 2.00000
3 -52.7981 2.00000
et kpoint2.dat
:
kpoint1 : 0.0000 0.0000 0.0000
band No. band energies occupation
1 -52.8287 2.00000
2 -52.7981 2.00000
3 -52.7981 2.00000
J'ai écrit un petit script pour le faire. Le script est présenté ci-dessous.
for j in {1..2}
do
awk '$1=="kpoint'$j'" {for(i=1; i<=3; i++){getline; print}}' tmp7 >kpoint'$j'.dat
done
Le script crée des fichiers de sortie avec les noms souhaités. Mais tous les fichiers sont vides. Quelqu'un peut-il m'aider à résoudre ce problème?
Cela peut être fait entièrement dans awk
:
$ awk '$1 ~ /kpoint[0-9]/ { file = $1 ".dat" } {print > file}' file
$ head kpoint*
==> kpoint1.dat <==
kpoint1 : 0.0000 0.0000 0.0000
band No. band energies occupation
1 -52.8287 2.00000
2 -52.7981 2.00000
3 -52.7981 2.00000
==> kpoint2.dat <==
kpoint2 : 0.0000 0.0000 0.0000
band No. band energies occupation
1 -52.8287 2.00000
2 -52.7981 2.00000
3 -52.7981 2.00000
Awk supporte également > file
pour la redirection, avec quelques différences subtiles (voir manuel de GNU awk pour plus).
Bien que réponse de mur soit le plus simple, il existe plusieurs autres moyens sans utiliser awk.
L'approche avec awk consiste essentiellement à écrire dans un nom de fichier spécifique et à modifier ce nom de fichier si et seulement si nous rencontrons kpoint au début de la ligne. La même approche peut être faite avec Perl:
$ Perl -ane '$p=$F[0] if $F[0] =~ /kpoint/;open($f,">>",$p . ".dat"); print $f $_' input.txt
Voici comment cela fonctionne:
-a
nous permet d'utiliser le tableau spécial @F
de mots automatiquement séparés de chaque ligne du fichier d'entrée. Ainsi, $F[0]
fait référence au premier mot, tout comme $1
in awk$p=$F[0] if $F[0] =~ /kpoint/
est censé modifier $p
(qui est censé être une variable de préfixe) si et seulement si kpoint
est dans la ligne. L'amélioration de cette correspondance de motif pourrait être /^ *kpoint/
à chaque itération, nous ouvrons un fichier en ajoutant à un nom portant le nom $p
associé à .dat
chaîne; notez que l'ajout d'une partie est important. Si vous voulez que tout soit clair, vous voulez probablement vous débarrasser des anciens fichiers kpoint
. Si nous voulons que le fichier soit toujours créé frais et écrasé, nous pouvons ré-écrire la commande originale en tant que:
$ Perl -ane 'if ($F[0] =~ /kpoint/){$p=$F[0]; open($f,">",$p . ".dat")}; print $f $_' input.txt
print $f $_
imprime simplement le nom de fichier que nous avons ouvert.D'après votre exemple, il apparaît que chaque entrée est composée de 5 lignes. Si cela est constant, nous pouvons diviser le fichier de cette façon, sans recourir à la correspondance de modèle avec split
. Plus précisément cette commande:
$ split --additional-suffix=".dat" --numeric-suffixes=1 -l 5 input.txt kpoint
Dans cette commande, les options sont les suivantes:
--additional-suffix=".dat"
est le suffixe statique .dat
qui sera ajouté à chaque fichier créé.--numeric-suffixes=1
nous permettra d'ajouter des numéros de changement commençant par 1 à chaque nom de fichier-l 5
permettra de fractionner le fichier d'entrée toutes les 5 lignesinput.txt
est le fichier que nous essayons de scinderkpoint
sera le préfixe statique du nom de fichierEt voici comment cela fonctionne dans la pratique:
$ split --additional-suffix=".dat" --numeric-suffixes=1 -l 5 input.txt kpoint
$ cat kpoint01.dat
kpoint1 : 0.0000 0.0000 0.0000
band No. band energies occupation
1 -52.8287 2.00000
2 -52.7981 2.00000
3 -52.7981 2.00000
$ cat kpoint02.dat
kpoint2 : 0.0000 0.0000 0.0000
band No. band energies occupation
1 -52.8287 2.00000
2 -52.7981 2.00000
3 -52.7981 2.00000
Facultativement, nous pourrions également ajouter --suffix-length=1
pour garder la longueur de chaque suffixe numérique plus courte, comme kpoint1
au lieu de kpoint01
, mais cela pourrait poser problème si vous avez un grand nombre de kpoint
s.
Celui-ci est similaire à réponse de mur , sauf qu'ici nous utilisons une correspondance de modèle différente ainsi qu'une approche différente pour créer la variable de nom de fichier via sprintf()
$ awk '/^\ *kpoint/{f=sprintf("%s.dat",$1)};{print > f}' input.txt
Alors que les approches awk
et split
sont plus courtes, d'autres outils tels que Python conviennent bien au traitement de texte, et nous pouvons les utiliser pour mettre en œuvre des solutions plus complètes mais fonctionnelles.
Le script ci-dessous fait exactement cela, et il repose sur l'idée de regarder en arrière dans la liste des lignes que nous sauvegardons. Le script conserve les lignes en attente jusqu'à ce qu'il rencontre kpoint
au début de la ligne, ce qui signifie que nous avons atteint une nouvelle entrée et que nous devons également écrire l'entrée précédente dans son fichier respectif.
#!/usr/bin/env python3
import sys
def write_entry(pref,line_list):
# this function writes the actual file for each entry
with open(".".join([pref,"dat"]),"w") as entry_file:
entry_file.write("".join(line_list))
def main():
prefix = ""
old_prefix = ""
entry=[]
with open(sys.argv[1]) as fd:
for line in fd:
# if we encounter kpoint string, that's a signal
# that we need to write out the list of things
if line.strip().startswith('kpoint'):
prefix=line.strip().split()[0]
# This if statement counters special case
# when we just started reading the file
if not old_prefix:
old_prefix = prefix
entry.append(line)
continue
write_entry(old_prefix,entry)
old_prefix = prefix
entry=[]
# Keep storing lines. This works nicely after old
# entry has been cleared out.
entry.append(line)
# since we're looking backwards, we need one last call
# to write last entry when input file has been closed
write_entry(old_prefix,entry)
if __== '__main__': main()
Presque la même idée que l'approche Perl - nous continuons à tout écrire dans un nom de fichier spécifique et ne modifions le nom de fichier que lorsque nous trouvons une ligne avec kpoint
dedans.
#!/usr/bin/env bash
while IFS= read -r line;
do
case "$line" in
# We found next entry. Use Word-splitting to get
# filename into fname variable, and truncate that filename
*kpoint[0-9]*) read fname trash <<< $line &&
echo "$line" > "$fname".dat ;;
# That's just a line within entry. Append to
# current working file
*) echo "$line" >> "$fname".dat ;;
esac
done < "$1"
# Just in case there are trailing lines that weren't processed
# in while loop, append them to last filename
[ -n "$line" ] && echo "$line" >> "$fname".dat ;