Erweitern Sie glob mit einem Flag, das vor jedem Dateinamen eingefügt wird

8

Ich habe ein Programm, das Argumente in der folgenden Syntax erwartet:

prog [-f filename | -g filename1 filename2] ...

Jedem Dateinamen muss das -fFlag vorangestellt werden . Die folgenden Aufrufe sind beispielsweise gültig für prog:

prog -f a.txt -g b.txt c.txt -f d.txt
prog -g a.txt b.txt -g c.txt d.txt
prog -f a.txt -f b.txt -f c.txt

… Aber die folgenden sind nicht:

prog -f a.txt b.txt
prog -f a.txt -g b.txt
prog a.txt

In meinem Fall interessiert mich nur die -fOption.

Ich habe viele Dateien in einem Verzeichnis, die alle auf enden .txt. Sie sehen so aus:

important-files/
├── a.txt
├── b.txt
├── c.txt
├── d.txt
└── filename with spaces.txt

Ich möchte vermeiden, dass jede Datei einzeln aufgelistet werden muss. Normalerweise würde ich dafür einen einfachen Glob verwenden:

$ prog important-files/*.txt

Dies funktioniert jedoch nicht, da der folgende ungültige Aufruf erzeugt wird:

$ prog important-files/a.txt important-files/b.txt important-files/c.txt important-files/d.txt 'important-files/filename with spaces.txt'

… Wenn ich diesen Aufruf wirklich will:

$ prog -f important-files/a.txt -f important-files/b.txt -f important-files/c.txt -f important-files/d.txt -f 'important-files/filename with spaces.txt'

… Da jedem Dateinamen ein Präfix vorangestellt werden muss, -fum progzu verstehen, dass sie nicht so interpretiert werden sollten -g.

Was ist der kürzeste Weg, um einen Glob zu verwenden und jeder der Dateien, auf die er erweitert wird, ein Flag voranzustellen?

Alexis King
quelle
2
Inzwischen ist dies in ZSH*.txt(P:\\-f:)
Uhr
3
@thrig Es ist nur *.txt(P:-f:)in der Tat (Warum sollten Sie dort einen Backslash setzen?).
Gilles 'SO - hör auf böse zu sein'

Antworten:

13

Verwenden printfund xargsunterstützende, nicht durch Trennzeichen getrennte Eingabe:

printf -- '-f\0%s\0' important-files/*.txt | xargs -0 prog

printfDurchläuft die Formatzeichenfolge über den Argumenten, sodass für jeden Dateinamen in der Erweiterung des Globs gedruckt -fund der Dateiname durch das Nullzeichen getrennt wird. xargsliest dies dann und konvertiert es in Argumente für prog. Die --seit einigen Implementierungen benötigten printfProbleme haben mit einem führenden -in der Formatzeichenfolge. Alternativ -kann das durch ersetzt werden \055, was Standard ist.

Gleiches Prinzip wie Rakeshs Antwort , jedoch direkt mit der Wildcard-Erweiterung.

muru
quelle
Warum ist die Verwendung von Nullzeichen erforderlich, warum nicht einfach doppelte Anführungszeichen wie : printf ' -f "%s"' important-files/*.txt | xargs prog?
MiniMax
4
Wenn die Dateinamen Anführungszeichen oder Backslashes enthalten, ist es schwierig, sie angemessen zu umgehen. Das Nullzeichen ist in Dateinamen nicht zulässig, daher ist es im Allgemeinen das sicherste Zeichen zum Trennen beliebiger (aber gültiger) Dateinamen.
Muru
Danke für die Antwort. In meinem Fall muss hinzugefügt werden --, da printf '-f\0%s\0' important-files/*.txtgibt der Fehler : bash: printf: -f: invalid option. Die printf -- '-f\0%s\0' important-files/*.txtArbeiten.
MiniMax
2
Mein Verständnis ist, dass xargsseine Eingabe verwendet wird, um Argumente an zu übergeben prog. Wenn jedoch zu viele Argumente vorhanden sind, werden diese aufgeteilt und progmehrmals aufgerufen . Haben wir hier die Möglichkeit, die Argumente an der falschen Stelle aufzuteilen, z. B. anzurufen prog -f a.txt -f b.txt ... -f m.txt -fund dann anzurufen prog n.txt -f o.txt ...?
Digitales Trauma
1
@ DigitalTrauma ja, die Möglichkeit besteht. Wäre dies jedoch der Fall, würde auch hier jede Antwort fehlschlagen, entweder aufgrund der Aufteilung oder aufgrund der Überschreitung der Argumentlängenbeschränkung. Ich zähle darauf, dass das Programm frühzeitig Argumente analysiert und natürlich hart versagt.
Muru
9

Versuchen Sie mit Bash:

args=()
for f in important-files/*.txt
do
    args+=(-f "$f")
done
prog "${args[@]}"

Beachten Sie, dass dies nicht nur mit Dateinamen funktioniert, die Leerzeichen enthalten, sondern auch mit Dateinamen, die einfache oder doppelte Anführungszeichen oder sogar Zeilenumbrüche enthalten.

Einfache interaktive Verwendung

Definieren Sie für eine einfache interaktive Verwendung eine Funktion:

progf() { args=(); for f in  "$@"; do args+=(-f "$f"); done; prog "${args[@]}"; }

Diese Funktion kann wie folgt verwendet werden:

progf important-files/*.txt

Um die Funktionsdefinition dauerhaft zu machen, platzieren Sie sie in Ihrer ~/.bashrcDatei.

John1024
quelle
Dies funktioniert zwar und ist in einem Skript etwas schmackhaft, aber es ist nicht besonders angenehm, wenn ich wirklich nur einen einmaligen Befehl interaktiv ausführen möchte.
Alexis King
@AlexisKing OK, aber für eine angenehme interaktive Verwendung würde ich eine Funktion definieren (siehe Update).
John1024
7

zshhat jetzt ein Pglob qualifier dafür .

Für ältere Versionen können Sie immer verwenden:

f=(*.txt)
prog -f$^f

$^fAktiviert die rcexpandparamOption für die Erweiterung von $f, wobei Arrays in einem ähnlichen Stil wie die rcShell erweitert werden.

In rc(oder zsh -o rcexpandparam):

f=(*.txt)
prog -f$f

Wird erweitert prog -fa.txt -fb.txt, ein bisschen als ob Sie in csh (oder anderen Shells, die die Erweiterung von Klammern unterstützen) geschrieben hätten : prog -f{a.txt,b.txt}.

(Beachten Sie, *.txt(P:-f:)dass sich der Dateiname von der Option unterscheidet -ffile.txt, anstatt zwei Argumente zu übergeben -f file.txt. Viele Befehle, einschließlich aller Befehle, die die Standard- getopt()API zum Parsen von Optionen verwenden, unterstützen beide Möglichkeiten, Argumente an Optionen zu übergeben.)

fish erweitert auch Arrays wie folgt:

set f *.txt
prog -f$f

In bashkönnen Sie es emulieren mit:

f=(*.txt)
prog "${f[@]/#/-f}"

Mit ksh93:

f=(*.txt)
prog "${f[@]/*/-f\0}"

Für -fund die entsprechenden file.txts, die als separate Argumente übergeben werden sollen, eine weitere Option bei zshVerwendung des Array-Zipping-Operators:

o=-f f=(*.txt); prog ${o:^^f}

Sie können dies für Ihre -gOption erweitern. Angenommen, es gibt eine gerade Anzahl von txtDateien:

o=(-g '') f=(*.txt); prog ${o:^^f}

Würde -gOptionen mit zwei Dateien gleichzeitig übergeben. Ähnlich wie bei:

printf -- '-g\0%s\0%s\0' *.txt | xargs -r0 prog
Stéphane Chazelas
quelle
Würde das Bash-Beispiel funktionieren? Sie erhalten Elemente wie -f a.txtstatt -f a.txt.
m0dular
1
@ m0dular, nein, am Ende haben Sie -fa.txtwie für die obigen zsh / rc / fish-Ansätze. Anwendungen, getopt()die es verwenden, behandeln es genauso wie -f a.txt, ich kann nichts über die Anwendung des OP sagen (obwohl es unwahrscheinlich ist, dass es verwendet wird, getopt()wenn es Optionen gibt, die mehr als ein Argument annehmen). Das ist in der Antwort erwähnt.
Stéphane Chazelas
6

Wie bereits in einem Kommentar erwähnt:

zsh -c 'prog *.txt(P:-f:)'

Dies verwendet das Glob-Qualifikationsmerkmal, P das jedem Glob-Match ein separates Wort voranstellt. Wie fast alle Glob-Qualifikanten von zsh hat bash nichts Vergleichbares.

Wenn Sie die Fertigstellung für möchten prog, führen Sie sie zshinteraktiv aus und geben Sie ein prog *.txt(P:-f:). Wenn Sie zsh zum ersten Mal interaktiv ausführen, werden Sie aufgefordert, eine Konfiguration festzulegen. Wenn Sie sich nicht für zsh interessieren, wählen Sie entweder die Option zum Erstellen eines Leerzeichens .zshrc(aber Sie werden einige zsh-Funktionen vermissen, die explizit aktiviert werden müssen) oder wählen 1Sie die Menüs aus und gehen Sie sie durch, um die empfohlenen Standardeinstellungen zu aktivieren.

Gilles 'SO - hör auf böse zu sein'
quelle
5

Eins. Methode dazu ist das GNU find / xargs Duo:

cd important-files && \
find . -name '*.txt'  -printf '-f\0%p\0' | xargs -0 -r  prog
Rakesh Sharma
quelle
Beachten Sie, dass dadurch das Arbeitsverzeichnis für geändert wird prog, was sich beispielsweise auf die vom Programm verwendeten relativen Pfade auswirken kann.
Kusalananda
2
Warum ändern Sie das Verzeichnis? Mach es einfach find important-files …stattdessen. Dadurch wird auch das von @Kusalananda erwähnte Problem vermieden.
Konrad Rudolph
Um zu vermeiden, dass das Argumentlimit überschritten wird, wird das Token "Wichtige Dateien" an alle Dateinamen angeheftet, ohne dass Informationen angezeigt werden.
Rakesh Sharma
1

POSIXly können Sie immer eine Funktion definieren wie:

prog_f() (
  for i do
    set -- "$@" -f "$i"
    shift
  done
  exec prog "$@"
)
Stéphane Chazelas
quelle
Warum wird die Unterschale exechier benötigt?
Muru