Angenommen, ich habe einen Ordner mit Dateien mit Namen wie
file1.txt
file2.txt
file2.txt
usw. Ich möchte für jeden einen Befehl ausführen, wie folgt:
mycommand file1.txt -o file1-processed.txt
mycommand file2.txt -o file2-processed.txt
mycommand file3.txt -o file3-processed.txt
usw.
Es gibt mehrere ähnliche Fragen auf dieser Site - der Unterschied besteht darin, dass ich den -processed
Test vor der Erweiterung in die Mitte des Dateinamens einfügen möchte .
Es scheint, find
sollte das Werkzeug für den Job sein. Ohne die -o
Flagge könnte ich das tun
find *.txt -exec mycommand "{}" ";"
Die {}
Syntax gibt jedoch den gesamten Dateinamen an, z file1.txt
. B. usw., sodass ich das " -processed
" nicht zwischen dem Dateinamen und seiner Erweiterung einfügen kann. Ein ähnliches Problem besteht bei der Verwendung einer einfachen Bash- for
Schleife.
Gibt es eine einfache Möglichkeit, diese Aufgabe mit find
oder auf andere Weise zu erfüllen ?
find *.txt
finden Sie alle Dateien und Verzeichnisse im aktuellen Verzeichnis, deren Namen mit enden.txt
, und dann alles unter diesen Verzeichnissen (falls vorhanden).Antworten:
Wenn sich alle zu verarbeitenden Dateien im selben Ordner befinden, müssen Sie sie nicht verwenden
find
und können mit nativem Shell-Globbing auskommen.Das Shell-Idiom
${foo%bar}
entfernt die kleinste Suffix-Zeichenfolge, die dem Muster entspricht,bar
aus dem Wert vonfoo
, in diesem Fall der.txt
Erweiterung, damit wir sie durch das gewünschte Suffix ersetzen können.quelle
mycommand
nur für reguläre Dateien aufgerufen wird , könnte man einfügen[ -f "$foo" ] &&
odertest -f "$foo" &&
davormycommand ...
.Wenn Sie ein Skript schreiben, das auf verschiedenen Systemen ausgeführt werden soll, und die Portabilität ein Problem darstellt , ist nichts besser als
for
Schleifen und die Antwort${var%sfx}
von user1404316 .Wenn Sie jedoch nach einer bequemen Möglichkeit suchen, ähnliche Dinge auf Ihrem eigenen System zu tun, empfehle ich von Herzen GNU Parallel , das sich hauptsächlich
xargs
auf Steroide bezieht. Hier ist eine Lösung für Ihre spezielle Aufgabe:Es wird eine Liste von Zeichenfolgen nach
:::
(Shell würde*.txt
zu einer Liste von übereinstimmenden Dateinamen erweitert) undmycommand {} -o {.}-processed.txt
für jede Zeichenfolge ausgeführt, wobei{}
durch Eingabezeichenfolge (genau wiexargs
) und{.}
durch Dateinamen ohne Erweiterung ersetzt wird.Sie können die Liste der Eingabezeichenfolgen über
stdin
( xargs -way) eingeben, um sie mitfind
oder zu koppelnlocate
, aber ich benötige selten mehr alszsh
die erweiterten Globs.Schauen Sie sich das GNU Parallel-Tutorial an, um eine Vorstellung davon zu bekommen, was es kann. Ich benutze es die ganze Zeit für Batch-Konvertierungen, das Extrahieren von Archiven in Sudirectories usw.
quelle
Dadurch wird die Antwort von user1404316 an folgende Bedingungen angepasst
find
:(Sie können das alles in eine Zeile eingeben; lassen Sie das einfach weg
\
. Ich habe es zur besseren Lesbarkeit in zwei Zeilen unterteilt.)Eine andere Möglichkeit, es für die Lesbarkeit zu formatieren, macht das eingebettete Shell-Skript ein bisschen klarer:
Grundsätzlich wird ein unbenanntes Shell-Skript erstellt:
(das ist die Zeichenfolge zwischen den einfachen Anführungszeichen,
'…'
abgewickelt) und übergibt sie als Befehl (sh -c
) mit den Namen aller Ihrer.txt
Dateien als Parameter an die Shell . (Sie müssen im Allgemeinen nicht zitieren{}
, und Sie brauchen keine geschweiften Klammern"$file"
.)quelle
'...'
) dadurch leicht zu sehen ist, ohne dass es separat erneut präsentiert werden muss. Aber ich bin ein bisschen ein Perfektionist in solchen Dingen. :)Mit
zsh
:$POSIXLY_CORRECT
sich in der Umgebung befinden. Das bedeutet auch, dass Sie Probleme mit Dateinamen, die mit-
(wie--
hier) beginnen, nicht umgehen können, außer mit./-file-.txt
.for var (values) cmd
ist eine praktische Kurzform der for-Schleife, die anperl
die Syntax erinnert.zsh
ist eine dieser seltenen Shells, bei denen$f
keine Anführungszeichen erforderlich sind (vorausgesetzt, sie befindet sich nicht in einem Modus, in dem andere Shells emuliert werden).$file:r
wird auf den Stammnamen der Datei erweitert, dh die Erweiterung wird wie in entferntcsh
. Es ist mehr oder weniger gleichbedeutend mit der Korn - Shell ist${f%.*}
mit dem zusätzlichen Vorteil , dass es keine Kreuz/
Grenzen (mitf=./foo
,$f:r
ist./foo
anstelle des leeren String), obwohl es hier nicht anwendbar..foo.txt
) aus. Wenn Sie möchten, dass sie eingeschlossen werden, können Sie dasD
glob-Qualifikationsmerkmal (*.txt(D)
) hinzufügen , aber Sie möchten möglicherweise eine Datei ausschließen, die.txt
alleine aufgerufen wird, da dann die Ausgabedatei-processed.txt
mit ideal wäre (und auch nicht ausgeblendet wäre)?*.txt(D)
.*.txt
im aktuellen Verzeichnis keine Datei befindet, schlägt dies mit einem Fehler fehl (und wird zum Glückmycommand
im Gegensatz zu anderen Shells überhaupt nicht ausgeführt). Wenn Sie möchten, dass die Schleife stattdessen nichts tut, können Sie dasN
glob-Qualifikationsmerkmal (*.txt(N)
) hinzufügen .Beachten Sie, dass dies alle
*.txt
Dateien unabhängig von ihrem Typ umfasst (regulär, Symlink, Verzeichnis, FIFO, Socket, Gerät ...). Wenn Sie nur reguläre Dateien berücksichtigen möchten , können Sie das.
Glob-Qualifikationsmerkmal hinzufügen (oder-.
auch Symlinks zu regulären Dateien hinzufügen, dh zu den Dateien, für die[ -f "$f" ]
true zurückgegeben wird). Also, mit all den oben genannten angewendet:*.txt
Um auch in Unterverzeichnissen nach Dateien zu suchen , ändern Sie diese in**/*.txt
(mit demD
Flag wird sie auch in versteckten Verzeichnissen angezeigt).quelle
Eine Kombination aus
find
,sed
undxargs
könnte funktionieren, wenn Sie die Rekursion und komplexe String - Ersetzung benötigen:Beispiel:
Der
-printf "%p\0-o\0%p\0"
druckt den Dateipfad zweimal, wobei-o
dazwischen das ASCII-NUL-Zeichen verwendet wird, und dersed
Befehl fügt in jeder dritten Zeile ein-processed
vor ein nachfolgendes Zeichen ein.txt
. Anschließend wirdxargs
der Befehl mit drei Argumenten gleichzeitig ausgeführt, nämlich dem Dateinamen-o
und dem bearbeiteten Dateinamen.quelle
Man könnte immer den
basename
Befehl verwenden, der Teil der GNU-Coreutils ist. Aus dem Verzeichnis mit den Dateien:quelle