Wie kann ich inverse oder negative Platzhalter bei Musteranpassung in einem Unix / Linux-Shell?
-
03-07-2019 - |
Frage
Sagen, dass ich den Inhalt eines Verzeichnisses kopieren möchten Ausschließen von Dateien und Ordner, deren Namen das Wort ‚Musik‘.
cp [exclude-matches] *Music* /target_directory
Was anstelle von [exclude-Treffer] gehen sollte dies zu erreichen?
Lösung
In Bash können Sie es tun, indem Sie die extglob
Option aktivieren, so (ersetzen ls
mit cp
und das Zielverzeichnis hinzufügen, natürlich)
~/foobar> shopt extglob
extglob off
~/foobar> ls
abar afoo bbar bfoo
~/foobar> ls !(b*)
-bash: !: event not found
~/foobar> shopt -s extglob # Enables extglob
~/foobar> ls !(b*)
abar afoo
~/foobar> ls !(a*)
bbar bfoo
~/foobar> ls !(*foo)
abar bbar
Sie können später deaktivieren extglob mit
shopt -u extglob
Andere Tipps
Die extglob
Shell Option gibt Ihnen leistungsfähigeren Musterabgleich in der Befehlszeile.
Sie es mit shopt -s extglob
einschalten, und schalten Sie es aus mit shopt -u extglob
.
In Ihrem Beispiel würden Sie zunächst tun:
$ shopt -s extglob
$ cp !(*Music*) /target_directory
Der vollständige verfügbar ext beendet glob bing Operatoren sind (Auszug aus man bash
):
Wenn die extglob Shell-Option aktiviert wird, um die shopt builtin verwendet wird, mehrere verlängert Pattern-Matching-Operatoren werden erkannt. In der folgenden Beschreibung wird ein pat tern-Liste ist eine Liste von einem oder mehreren Mustern durch |. Composite-Muster kann unter Verwendung eines oder mehrerer der folgenden Teilmuster gebildet werden:
- ? (Muster-Liste)
Null oder ein Vorkommen der gegebenen Muster- * (Muster-Liste)
Spiele null oder mehr Vorkommen der gegebenen Muster- + (Muster-Liste)
Spiele ein oder mehrere Vorkommen der gegebenen Muster- @ (Muster-Liste)
Spiele eines der angegebenen Muster- ! (Muster-Liste)
Spiele nichts außer einem des gegebenen Musters
So zum Beispiel, wenn Sie alle Dateien im aktuellen Verzeichnis aufzulisten wollen, die nicht .c
oder .h
Dateien, würden Sie tun:
$ ls -d !(*@(.c|.h))
Natürlich normale Shell Ballung funktioniert, so das letzte Beispiel auch geschrieben werden:
$ ls -d !(*.[ch])
Nicht in bash (die ich kenne), aber:
cp `ls | grep -v Music` /target_directory
Ich weiß, das ist nicht genau das, was Sie suchen, aber es wird Ihr Beispiel lösen.
Wenn Sie die mem Kosten für die Verwendung mit dem Befehl exec vermeiden wollen, glaube ich Sie mit xargs besser machen können. Ich denke, die folgende ist eine effiziente Alternative zu
find foo -type f ! -name '*Music*' -exec cp {} bar \; # new proc for each exec
find . -maxdepth 1 -name '*Music*' -prune -o -print0 | xargs -0 -i cp {} dest/
In der bash eine Alternative zu shopt -s extglob
ist die GLOBIGNORE
Variable . Es ist nicht wirklich besser, aber ich finde es leichter zu merken.
Ein Beispiel, das kann sein, was das ursprüngliche Plakat wollte:
GLOBIGNORE="*techno*"; cp *Music* /only_good_music/
Wenn Sie fertig sind, unset GLOBIGNORE
der Lage sein, im Quellverzeichnis rm *techno*
.
Sie können auch eine ziemlich einfache for
Schleife verwenden:
for f in `find . -not -name "*Music*"`
do
cp $f /target/dir
done
Meine persönliche Präferenz ist grep und den während Befehl zu verwenden. Dies ermöglicht eine leistungsstarke und dennoch lesbar Skripte schreiben sicherzustellen, dass Sie genau am Ende tun, was Sie wollen. Plus durch einen Echo-Befehl können Sie einen Testlauf durchführen, bevor die eigentliche Operation durchgeführt wird. Zum Beispiel:
ls | grep -v "Music" | while read filename
do
echo $filename
done
wird aus den Dateien drucken, die Sie kopieren landen. Wenn die Liste korrekt ist der nächste Schritt besteht darin, einfach den Befehl echo mit dem Kopierbefehl wie folgt zu ersetzen:
ls | grep -v "Music" | while read filename
do
cp "$filename" /target_directory
done
Eine Lösung hierfür kann mit find gefunden werden.
$ mkdir foo bar
$ touch foo/a.txt foo/Music.txt
$ find foo -type f ! -name '*Music*' -exec cp {} bar \;
$ ls bar
a.txt
Suche schon einige Optionen hat, können Sie ziemlich spezifisch bekommen, was Sie sind und ausgeschlossen werden.
Edit: Adam in den Kommentaren darauf hingewiesen, dass dieser rekursiv. finden Optionen mindepth und maxdepth kann bei der Kontrolle dieses nützlich sein.
Ein Trick, den ich hier noch nicht gesehen hat, dass nicht extglob
, find
oder grep
nicht verwendet zwei Dateilisten als Sätze zu behandeln und „diff“ sie mit comm
:
comm -23 <(ls) <(ls *Music*)
comm
ist vorzuziehen diff
, weil es keine zusätzlichen cruft hat.
Dies gibt alle Elemente des Satzes 1, ls
, das ist nicht auch in Satz 2, ls *Music*
. Dies erfordert beiden Sätze in sortierter Reihenfolge zu sein, richtig zu arbeiten. Kein Problem für ls
und glob Expansion, aber wenn Sie so etwas wie find
verwenden, müssen Sie sort
aufzurufen.
comm -23 <(find . | sort) <(find . | grep -i '.jpg' | sort)
potentiell nützlich.
Das folgende Werk listet alle *.txt
Dateien im aktuellen Verzeichnis, mit Ausnahme derjenigen, die mit einer Zahl beginnen.
Das funktioniert in bash
, dash
, zsh
und alle anderen POSIX-kompatiblen Shells.
for FILE in /some/dir/*.txt; do # for each *.txt file
case "${FILE##*/}" in # if file basename...
[0-9]*) continue ;; # starts with digit: skip
esac
## otherwise, do stuff with $FILE here
done
-
Im Rahmen eines das Muster
/some/dir/*.txt
diefor
Schleife verursacht alle Dateien in/some/dir
, dessen Namen mit.txt
Ende iterieren. -
In Zeile zwei eine Case-Anweisung verwendet wird, unerwünschte Dateien auszusortieren. - Der
${FILE##*/}
Ausdruck Streifen jede führend dir name Komponente aus dem Dateinamen aus (hier/some/dir/
), so dass prasselt gegen nur den Basisnamen der Datei übereinstimmen kann. (Wenn Sie nur Dateinamen Ausmerzung basierend auf Suffixe, können Sie diese verkürzen, anstatt$FILE
.) -
In Zeile drei, werden alle Dateien, die
case
Muster[0-9]*
) Linie entsprechen, werden übersprungen werden (diecontinue
Anweisung springt zum nächsten Iteration derfor
Schleife). - Wenn Sie möchten, können Sie hier etwas Interessanteres tun, zum Beispiel wie alle Dateien übersprungen, die nicht mit einem Buchstaben (a-z) mit[!a-z]*
beginnen oder Sie können mehrere Muster verwenden, um verschiedene Arten von Dateinamen zum Beispiel überspringen[0-9]*|*.bak
zu überspringen Dateien sowohl.bak
-Dateien und Dateien, die nicht mit einer Zahl beginnen.
Dies würde es tun, ohne genau ‚Musik‘
cp -a ^'Music' /target
dies und das für das Ausschließen Dinge wie Musik? * Oder *? Musik
cp -a ^\*?'complete' /target
cp -a ^'complete'?\* /target