Ich verfüge aus wissenschaftlichen Praktika, an denen ich teilgenommen habe, über einige Erfahrungen mit Inix-Terminals und habe dabei hauptsächlich einige Dienstprogramme wie grep
, awk
, und verwendet sed
. Es gibt jedoch eine Sache, die ich schon seit einiger Zeit herauszufinden versuche, um die Zahlenverarbeitung, die ich durchführen muss, wirklich viel effizienter zu gestalten.
Ich habe ein Skript run.awk
, das einige Manipulationen an einer großen Sammlung massiver Textdateien vornimmt. Es nimmt die Datei chloride.out
, extrahiert Daten daraus und schreibt sie chloride.cm
.
Gibt es eine Möglichkeit, dieses Skript dazu zu bringen, Dateien basierend auf der anfänglichen Platzhalterphrase in der Shell aufzunehmen *.out
und zu schreiben ?*.cm
Die Menge an Skripten, die ich zur Verarbeitung großer Datenmengen geschrieben habe und für die ich mehr als hundert Iterationen durchführen musste, ist einfach ärgerlich.
Am liebsten würde ich wissen, ob es eine Möglichkeit gibt, dies für alle meine Skripte über die Shell zu tun. Wenn es nicht in der Shell oder einem Äquivalent automatisiert werden kann, kann ich meine awk
Skripte dann zumindest auf ähnliche Weise automatisieren, wie ich es beschrieben habe?
Antwort1
Sie können awk mit Sicherheit dazu bringen, mehrere Dateien über Platzhalter zu verarbeiten. Ein Vorschlag wäre, es run.awk
als generische „Funktion“ zu belassen, die eine einzelne Datei annimmt und eine einzelne Ausgabedatei erzeugt, und es dann von einem anderen Skript aus aufzurufen, das sich dann um die Assimilation der Eingabe- und Ausgabedateien kümmern könnte.
Beispiel
Dies wäre ein Bash-Skript, wir können es nennen awk_runner.bash
.
#!/bin/bash
for ifname in *.out; do
ofname=${ifname/.out/.cm}
printf "IN: %s, OUT: %s\n" $ifname $ofname
printf "running run.awk with %s & %s\n\n" $ifname $ofname
run.awk $ifname $ofname
done
Beispiellauf
Ich habe ein Beispielverzeichnis mit einigen Testdateien darin erstellt.
$ touch file{1..4}.out
Dadurch wurden 4 Dateien erstellt:
$ ls -1
file1.out
file2.out
file3.out
file4.out
Jetzt führen wir unser Skript aus:
$ ./awk_runner.bash
IN: file1.out, OUT: file1.cm
running run.awk with file1.out & file1.cm
IN: file2.out, OUT: file2.cm
running run.awk with file2.out & file2.cm
IN: file3.out, OUT: file3.cm
running run.awk with file3.out & file3.cm
IN: file4.out, OUT: file4.cm
running run.awk with file4.out & file4.cm
Nach jeder Zeile, die mit „running…“ beginnt, könnte unser Skript von hier aus ausgeführt werden.
Dateien in einer Liste
Angenommen, wir verwenden statt des Platzhalters *.out
eine Datei mit einer Liste von Dateinamen, etwa:
$ cat filelist.txt
file1.out
file2.out
file3.out
file4.out
Wir könnten diese modifizierte Version unseres Skripts verwenden, die eine while
Schleife anstelle einer for
Schleife verwendet. Nennen wir nun diese Variante des Skripts awk_file_runner.bash
:
#!/bin/bash
while read ifname; do
ofname=${ifname/.out/.cm}
printf "IN: %s, OUT: %s\n" $ifname $ofname
printf "running run.awk with %s & %s\n\n" $ifname $ofname
run.awk $ifname $ofname
done < filelist.txt
Diese Version des Skripts liest die Eingabe aus der Datei filelist.txt
:
done < filelist.txt
while
Dann verwenden wir für jeden Durchlauf der Schleife den read
Befehl, um eine Zeile aus der Eingabedatei einzulesen.
while read ifname; do
awk
Anschließend führt es alles auf die gleiche Weise wie das erste Skript aus, indem es das Skript ausführt, run.awk
während es jede Zeile der Datei durchläuft.
Antwort2
Anstatt einen Shell-Wrapper zu schreiben und für jede verarbeitete Datei eine neue awk-Instanz zu erzeugen, können Sie dies direkt in awk tun. Wenn Sie bereits ein awk-Skript haben, können Sie mit der Variable FILENAME auf die aktuelle Datei zugreifen. Wenn Sie also ausführen , können Sie mit FILENAME feststellen, ob Sie mit Datei1 oder Datei2 arbeiten. Sie können auch on / in awk awk 'some commands' file1 file2
verwenden . Wenn Sie also ein awk-Skript wie haben>
print
printf
/pattern/{ print $1,$3 }
Sie könnten leicht tun
/pattern/{ print $1,$3 > FILENAME".processed" }
oder verwenden Sie es, FNR=1
um zu erkennen, wenn Sie sich in einer neuen Datei befinden, und erstellen Sie eine Variable, um komplexere Manipulationen am Dateinamen vorzunehmen. Wie das Ersetzen einer .in
Erweiterung durch .out
, wie in
sauer@humpy:/tmp$ grep . file*.in
file1.in:a
file1.in:b
file2.in:c
sauer@humpy:/tmp$ awk 'FNR=1{out=FILENAME;sub("\.in$",".out",out)} {print "processed"$0 > out}' file*.in
sauer@humpy:/tmp$ grep . file*.out
file1.out:processeda
file1.out:processedb
file2.out:processedc
Ich verwende grep .
hier, um den Dateinamen und den Inhalt mehrerer Dateien anzuzeigen, was auch ein lustiger Trick ist. Aber das Wichtigste ist, den Wert der out
Variablen auf eine geänderte Version von zu setzen, FILENAME
wenn FNR
sich dieser auf 1 ändert (damit wir uns in Zeile 1 der Datei befinden) und dann alle Ausdrucke auf umzuleiten . Beachten Sie, dass dies leicht gefährlich ist, da eine Nichtübereinstimmung der Erweiterung zu keiner Ersetzung führt und Ihre Eingabedateien überschrieben werden. Daher wäre es gut, eine ausfallsichere Prüfung hinzuzufügen, um dies oder etwas Ähnliches out
sicherzustellen . Das bleibt dem Leser als Übung überlassen. ;)out != FILENAME
Wenn Sie eine Datei mit einer Liste von Dateinamen benötigen, ist es am einfachsten, sie wie folgt auszuführen:
awkscript $(< /path/to/filename_list_file )
Dadurch wird der Inhalt übernommen filename_list_file
und in die Befehlszeile eingefügt.