Testen Sie, ob ein Glob Übereinstimmungen in Bash hat

223

Wenn ich prüfen möchte, ob eine einzelne Datei vorhanden ist, kann ich sie mit test -e filenameoder testen [ -e filename ].

Angenommen, ich habe einen Glob und möchte wissen, ob Dateien vorhanden sind, deren Namen mit dem Glob übereinstimmen. Der Glob kann mit 0 Dateien übereinstimmen (in diesem Fall muss ich nichts tun) oder mit 1 oder mehreren Dateien (in diesem Fall muss ich etwas tun). Wie kann ich testen, ob ein Glob Übereinstimmungen hat? (Es ist mir egal, wie viele Übereinstimmungen es gibt, und es wäre am besten, wenn ich dies mit einer ifAnweisung und ohne Schleifen tun könnte (einfach, weil ich das am besten lesbar finde).

( test -e glob*schlägt fehl, wenn der Glob mit mehr als einer Datei übereinstimmt.)

Ken Bloom
quelle
3
Ich vermute, dass meine Antwort unten "eindeutig richtig" ist, so dass alle anderen irgendwie herumhacken. Es handelt sich um eine einzeilige Shell-Version, die es schon immer gab und die als "das vorgesehene Werkzeug für diesen bestimmten Job" erscheint. Ich mache mir Sorgen, dass Benutzer hier fälschlicherweise auf die akzeptierte Antwort verweisen. Jeder kann mich gerne korrigieren und ich werde meinen Kommentar hier zurückziehen. Ich bin mehr als glücklich, falsch zu liegen und daraus zu lernen. Wenn der Unterschied nicht so drastisch wäre, würde ich dieses Problem nicht ansprechen.
Brian Chrisman
1
Meine bevorzugten Lösungen für diese Frage sind der Befehl find, der in jeder Shell funktioniert (auch in Nicht-Bourne-Shells), aber GNU find erfordert, und der Befehl compgen, der eindeutig ein Bashismus ist. Schade, dass ich nicht beide Antworten akzeptieren kann.
Ken Bloom
Hinweis: Diese Frage wurde bearbeitet, seit sie gestellt wurde. Der ursprüngliche Titel lautete "Teste, ob ein Glob Übereinstimmungen in Bash hat". Die spezifische Shell 'bash' wurde aus der Frage entfernt, nachdem ich meine Antwort veröffentlicht hatte. Die Bearbeitung des Titels der Frage lässt meine Antwort fehlerhaft erscheinen. Ich hoffe, jemand kann diese Änderung ändern oder zumindest angehen.
Brian Chrisman

Antworten:

178

Bash- spezifische Lösung:

compgen -G "<glob-pattern>"

Entkomme dem Muster oder es wird zu Übereinstimmungen vorerweitert.

Der Exit-Status lautet:

  • 1 für Nichtübereinstimmung,
  • 0 für 'eine oder mehrere Übereinstimmungen'

stdoutist eine Liste von Dateien, die dem Glob entsprechen .
Ich denke, dies ist die beste Option in Bezug auf Prägnanz und Minimierung möglicher Nebenwirkungen.

UPDATE : Beispiel für eine Verwendung angefordert.

if compgen -G "/tmp/someFiles*" > /dev/null; then
    echo "Some files exist."
fi
Brian Chrisman
quelle
9
Beachten Sie, dass dies compgenein bash- spezifischer integrierter Befehl ist und nicht Teil der von der POSIX-Standard-Unix-Shell angegebenen integrierten Befehle ist. pubs.opengroup.org/onlinepubs/9699919799 pubs.opengroup.org/onlinepubs/9699919799/utilities/… Vermeiden Sie daher die Verwendung in Skripten, in denen die Portabilität auf andere Shells ein Problem darstellt .
Diomidis Spinellis
1
Es scheint mir, dass ein ähnlicher Effekt ohne Bash-Buildins darin besteht, einen anderen Befehl zu verwenden, der auf einen Glob wirkt und fehlschlägt, wenn keine Dateien übereinstimmen, wie z. B. ls: if ls /tmp/*Files 2>&1 >/dev/null; then echo exists; fi- Vielleicht nützlich für Code-Golf? Schlägt fehl, wenn es eine Datei mit dem gleichen Namen wie der Glob gibt, mit der der Glob nicht hätte übereinstimmen sollen, aber wenn dies der Fall ist, haben Sie wahrscheinlich größere Probleme.
Dewi Morgan
4
@ DewiMorgan Dies ist einfacher:if ls /tmp/*Files &> /dev/null; then echo exists; fi
Clay Bridges
Für Details über compgen, siehe man bashoder mithelp compgen
el-teedee
2
Ja, zitieren Sie es oder der Platzhalter für den Dateinamen wird vorab erweitert. compgen "dir / *. ext"
Brian Chrisman
169

Die Nullglob-Shell-Option ist in der Tat ein Bashismus.

Um zu vermeiden, dass der Nullglob-Status mühsam gespeichert und wiederhergestellt werden muss, habe ich ihn nur in der Subshell festgelegt, die den Glob erweitert:

if test -n "$(shopt -s nullglob; echo glob*)"
then
    echo found
else
    echo not found
fi

Verwenden Sie find für eine bessere Portabilität und flexibleres Globbing:

if test -n "$(find . -maxdepth 1 -name 'glob*' -print -quit)"
then
    echo found
else
    echo not found
fi

Explizite -print-quit- Aktionen werden anstelle der impliziten Standard- print- Standardaktion für die Suche verwendet, sodass die Suche beendet wird, sobald die erste Datei gefunden wird, die den Suchkriterien entspricht. Wenn viele Dateien übereinstimmen, sollte dies viel schneller als oder ausgeführt werden, und es wird auch die Möglichkeit vermieden, die erweiterte Befehlszeile zu überfüllen (einige Shells haben eine Längenbeschränkung von 4 KB).echo glob*ls glob*

Wenn sich find wie übertrieben anfühlt und die Anzahl der Dateien, die wahrscheinlich übereinstimmen, gering ist, verwenden Sie stat:

if stat -t glob* >/dev/null 2>&1
then
    echo found
else
    echo not found
fi
flabdablet
quelle
10
findscheint genau richtig zu sein. Es gibt keine Eckfälle, da die Shell keine Erweiterung durchführt (und einen nicht erweiterten Glob an einen anderen Befehl übergibt), zwischen Shells portierbar ist (obwohl anscheinend nicht alle von Ihnen verwendeten Optionen von POSIX angegeben werden) und schneller als ls -d glob*(die zuvor akzeptierte Antwort), weil sie stoppt, wenn sie das erste Spiel erreicht.
Ken Bloom
1
Beachten Sie, dass für diese Antwort möglicherweise ein erforderlich ist, shopt -u failglobda diese Optionen in irgendeiner Weise in Konflikt zu geraten scheinen.
Calimo
Die findLösung stimmt auch mit einem Dateinamen ohne Glob-Zeichen überein. In diesem Fall wollte ich das. Nur etwas zu beachten.
Wir sind alle Monica
1
Da jemand anderes beschlossen hat, meine Antwort zu bearbeiten, damit sie das anscheinend sagt.
Flabdablet
1
unix.stackexchange.com/questions/275637/… erläutert, wie die -maxdepthOption für einen POSIX-Fund ersetzt wird.
Ken Bloom
25
#!/usr/bin/env bash

# If it is set, then an unmatched glob is swept away entirely -- 
# replaced with a set of zero words -- 
# instead of remaining in place as a single word.
shopt -s nullglob

M=(*px)

if [ "${#M[*]}" -ge 1 ]; then
    echo "${#M[*]} matches."
else
    echo "No such files."
fi
miku
quelle
2
Um mögliche falsche „Keine Übereinstimmungen“ zu vermeiden, nullglobanstatt zu prüfen, ob ein einzelnes Ergebnis dem Muster selbst entspricht. Einige Muster können mit Namen übereinstimmen, die genau dem Muster selbst entsprechen (z a*b. B. aber nicht zB a?boder [a]).
Chris Johnsen
Ich nehme an, dies scheitert an der höchst unwahrscheinlichen Wahrscheinlichkeit, dass es tatsächlich eine Datei mit dem Namen glob gibt. (zB jemand rannte touch '*py'), aber das weist mich in eine andere gute Richtung.
Ken Bloom
Ich mag diese als die allgemeinste Version.
Ken Bloom
Und auch am kürzesten. Wenn Sie nur ein Spiel erwarten, können Sie es "$M"als Abkürzung für verwenden "${M[0]}". Andernfalls haben Sie die Glob-Erweiterung bereits in einer Array-Variablen, sodass Sie sie als Liste an andere Dinge übergeben müssen, anstatt sie dazu zu bringen, den Glob erneut zu erweitern.
Peter Cordes
Nett. Sie können M schneller testen (weniger Bytes und ohne einen [Prozess zu erzeugen ) mitif [[ $M ]]; then ...
Tobia
22

ich mag

exists() {
    [ -e "$1" ]
}

if exists glob*; then
    echo found
else
    echo not found
fi

Dies ist sowohl lesbar als auch effizient (es sei denn, es gibt eine große Anzahl von Dateien).
Der Hauptnachteil ist, dass es viel subtiler ist als es aussieht, und ich fühle mich manchmal gezwungen, einen langen Kommentar hinzuzufügen.
Wenn es eine Übereinstimmung gibt, "glob*"wird diese von der Shell erweitert und alle Übereinstimmungen werden an übergeben exists(), wodurch die erste überprüft und der Rest ignoriert wird.
Wenn es keine Übereinstimmung gibt, "glob*"wird an übergeben exists()und festgestellt, dass es dort auch nicht existiert.

Bearbeiten: Möglicherweise liegt ein falsches Positiv vor, siehe Kommentar

Dan Bloch
quelle
13
Es kann ein falsches Positiv zurückgeben, wenn es sich bei dem Glob um etwas handelt *.[cC](es gibt möglicherweise keine coder eine CDatei, aber eine aufgerufene Datei *.[cC]), oder ein falsches Negativ, wenn die erste daraus erweiterte Datei beispielsweise ein Symlink zu einer nicht vorhandenen Datei oder zu einer Datei in a ist Verzeichnis, auf das Sie keinen Zugriff haben (Sie möchten übrigens ein hinzufügen || [ -L "$1" ]).
Stephane Chazelas
Interessant. Shellcheck meldet, dass Globbing nur funktioniert -e, wenn 0 oder 1 Übereinstimmungen vorliegen . Es funktioniert nicht für mehrere Übereinstimmungen, da dies zu einem [ -e file1 file2 ]Fehlschlag führen würde. Unter github.com/koalaman/shellcheck/wiki/SC2144 finden Sie auch die Gründe und Lösungsvorschläge.
Thomas Praxl
10

Wenn Sie Globfail eingestellt haben, können Sie dieses verrückte verwenden (was Sie wirklich nicht sollten)

shopt -s failglob # exit if * does not match 
( : * ) && echo 0 || echo 1

oder

q=( * ) && echo 0 || echo 1
James
quelle
2
Eine fantastische Verwendung eines Noop-Fehlers. Niemals benutzt werden ... aber wirklich schön. :)
Brian Chrisman
Sie können den Shopt in die Parens legen. Auf diese Weise wirkt es sich nur auf den Test aus:(shopt -s failglob; : *) 2>/dev/null && echo exists
flabdablet
8

test -e hat die unglückliche Einschränkung, dass defekte symbolische Links nicht existieren. Vielleicht möchten Sie auch nach diesen suchen.

function globexists {
  test -e "$1" -o -L "$1"
}

if globexists glob*; then
    echo found
else
    echo not found
fi
NerdMachine
quelle
4
Das behebt das Falsch-Positive bei Dateinamen, die Glob-Sonderzeichen enthalten, immer noch nicht, wie Stephane Chazelas für Dan Blochs Antwort hervorhebt. (es sei denn, Sie Affe mit Nullglob).
Peter Cordes
3
Sie sollten vermeiden -ound -ain test/ [. Hier schlägt dies $1beispielsweise =bei den meisten Implementierungen fehl . Verwenden Sie [ -e "$1" ] || [ -L "$1" ]stattdessen.
Stephane Chazelas
4

Um die Antwort des MYYN etwas zu vereinfachen, basierend auf seiner Idee:

M=(*py)
if [ -e ${M[0]} ]; then
  echo Found
else
  echo Not Found
fi
Ken Bloom
quelle
4
Schließen, aber was ist, wenn Sie übereinstimmen [a], eine Datei mit dem Namen haben [a], aber keine Datei mit dem Namen a? Ich mag nullglobdas immer noch. Einige mögen dies als pedantisch ansehen, aber wir könnten genauso gut richtig sein, wie es vernünftig ist.
Chris Johnsen
@ sondra.kinsey Das ist falsch; Der Glob [a]sollte nur übereinstimmen a, nicht der wörtliche Dateiname [a].
Tripleee
4

Basierend auf der Antwort von flabdablet sieht es für mich so aus, als ob es am einfachsten (nicht unbedingt am schnellsten) ist, sich selbst zu finden , während die Glob-Erweiterung auf der Shell verbleibt, wie:

find /some/{p,long-p}ath/with/*globs* -quit &> /dev/null && echo "MATCH"

Oder ifwie folgt:

if find $yourGlob -quit &> /dev/null; then
    echo "MATCH"
else
    echo "NOT-FOUND"
fi
Queria
quelle
Dies funktioniert genau wie die Version, die ich bereits mit stat vorgestellt habe. Ich bin mir nicht sicher, wie das Finden "einfacher" ist als stat.
Flabdablet
3
Beachten Sie, dass &> Umleitung ein Bashismus ist und in anderen Shells leise das Falsche tut.
Flabdablet
Dies scheint besser zu sein als die findAntwort von flabdablet, da es Pfade im Glob akzeptiert und knapper ist (nicht erforderlich -maxdepthusw.). Es scheint auch besser zu sein als seine statAntwort, da es nicht weiterhin das Extra statfür jedes weitere Glob-Match leistet. Ich würde mich freuen, wenn jemand Eckfälle beitragen könnte, in denen dies nicht funktioniert.
Drwatsoncode
1
Nach weiteren Überlegungen würde ich hinzufügen, -maxdepth 0weil es mehr Flexibilität beim Hinzufügen von Bedingungen ermöglicht. Angenommen, ich möchte das Ergebnis nur auf übereinstimmende Dateien beschränken. Ich könnte es versuchen find $glob -type f -quit, aber das würde true zurückgeben, wenn der Glob NICHT mit einer Datei übereinstimmt, sondern mit einem Verzeichnis, das eine Datei enthält (sogar rekursiv). Im Gegensatz dazu find $glob -maxdepth 0 -type f -quitwürde true nur zurückgegeben, wenn der Glob selbst mindestens einer Datei entspricht. Beachten Sie, dass maxdepthdies nicht verhindert, dass der Glob eine Verzeichniskomponente enthält. (FYI 2>ist ausreichend. Keine Notwendigkeit für &>)
drwatsoncode
2
Der Sinn der Verwendung findbesteht darin, zu vermeiden, dass die Shell eine potenziell große Liste von Glob-Übereinstimmungen generiert und sortiert. find -name ... -quitentspricht höchstens einem Dateinamen. Wenn ein Skript darauf angewiesen ist, eine von der Shell generierte Liste von Glob-Übereinstimmungen an zu übergeben find, wird durch das Aufrufen findnur ein unnötiger Prozessstartaufwand verursacht. Das einfache Testen der resultierenden Liste direkt auf Nichtleere ist schneller und klarer.
Flabdablet
4

Ich habe noch eine andere Lösung:

if [ "$(echo glob*)" != 'glob*' ]

Das funktioniert gut für mich. Gibt es einige Eckfälle, die ich vermisse?

SaschaZorn
quelle
2
Funktioniert nur, wenn die Datei tatsächlich den Namen 'glob *' trägt.
Ian Kelling
funktioniert für die Übergabe von glob als Variable - gibt den Fehler "zu viele Argumente" aus, wenn mehr als eine Übereinstimmung vorliegt. "$ (echo $ GLOB)" gibt keinen einzelnen String zurück oder wird zumindest nicht als einzelne Single interpretiert, daher der Fehler mit zu vielen Argumenten
DKebler
@DKebler: Es sollte als einfache Zeichenfolge interpretiert werden, da es in doppelte Anführungszeichen eingeschlossen ist.
user1934428
3

In Bash können Sie zu einem Array globalisieren. Wenn der Glob nicht übereinstimmt, enthält Ihr Array einen einzelnen Eintrag, der keiner vorhandenen Datei entspricht:

#!/bin/bash

shellglob='*.sh'

scripts=($shellglob)

if [ -e "${scripts[0]}" ]
then stat "${scripts[@]}"
fi

Hinweis: Wenn Sie festgelegt haben nullglob, handelt scriptses sich um ein leeres Array, und Sie sollten stattdessen mit [ "${scripts[*]}" ]oder mit testen [ "${#scripts[*]}" != 0 ]. Wenn Sie eine Bibliothek schreiben, die mit oder ohne funktionieren muss nullglob, möchten Sie

if [ "${scripts[*]}" ] && [ -e "${scripts[0]}" ]

Ein Vorteil dieses Ansatzes besteht darin, dass Sie dann die Liste der Dateien haben, mit denen Sie arbeiten möchten, anstatt den Glob-Vorgang wiederholen zu müssen.

Toby Speight
quelle
Warum können Sie mit gesetztem Nullglob und möglicherweise leerem Array nicht noch testen if [ -e "${scripts[0]}" ]...? Berücksichtigen Sie auch die Möglichkeit, die Nomenmenge der Shell-Option festzulegen ?
Johnraff
@ Johnraff, ja, ich gehe normalerweise davon aus, dass nounsetes aktiv ist. Außerdem kann es (etwas) billiger sein, zu testen, ob die Zeichenfolge nicht leer ist, als zu überprüfen, ob eine Datei vorhanden ist. Angesichts der Tatsache, dass wir gerade einen Glob ausgeführt haben, ist dies jedoch unwahrscheinlich. Dies bedeutet, dass der Verzeichnisinhalt im Cache des Betriebssystems aktuell sein sollte.
Toby Speight
1

Dieser Gräuel scheint zu funktionieren:

#!/usr/bin/env bash
shopt -s nullglob
if [ "`echo *py`" != "" ]; then
    echo "Glob matched"
else
    echo "Glob did not match"
fi

Es erfordert wahrscheinlich Bash, nicht sh.

Dies funktioniert, weil die Option nullglob bewirkt, dass der Glob als leere Zeichenfolge ausgewertet wird, wenn keine Übereinstimmungen vorhanden sind. Daher zeigt jede nicht leere Ausgabe des Echo-Befehls an, dass der Glob mit etwas übereinstimmt.

Ryan C. Thompson
quelle
Sie sollten verwendenif [ "`echo *py`" != "*py"]
yegle
1
Das würde nicht richtig funktionieren, wenn eine Datei aufgerufen würde *py.
Ryan C. Thompson
Wenn es kein Dateiende mit gibt py, `echo *py`wird ausgewertet *py.
Yegle
1
Ja, aber es wird auch so sein, wenn eine einzelne Datei aufgerufen wird *py, was das falsche Ergebnis ist.
Ryan C. Thompson
Korrigieren Sie mich, wenn ich falsch liege, aber wenn keine *pypassende Datei vorhanden ist, gibt Ihr Skript "Glob Matched" aus?
Yegle
1

Ich habe diese Antwort nicht gesehen und dachte, ich würde sie dort veröffentlichen:

set -- glob*
[ -f "$1" ] && echo "found $@"
Brad Howes
quelle
1
set -- glob*
if [ -f "$1" ]; then
  echo "It matched"
fi

Erläuterung

Wenn es keine Übereinstimmung für gibt glob*, $1wird enthalten 'glob*'. Der Test -f "$1"ist nicht wahr, da die glob*Datei nicht vorhanden ist.

Warum das besser ist als Alternativen

Dies funktioniert mit sh und Derivaten: ksh und bash. Es wird keine Sub-Shell erstellt. $(..)und `...`Befehle erstellen eine Unter-Shell; Sie geben einen Prozess vor und sind daher langsamer als diese Lösung.

Joseyluis
quelle
1

So in (Testdateien enthalten pattern):

shopt -s nullglob
compgen -W *pattern* &>/dev/null
case $? in
    0) echo "only one file match" ;;
    1) echo "more than one file match" ;;
    2) echo "no file match" ;;
esac

Es ist viel besser als compgen -G : weil wir mehr Fälle und genauer unterscheiden können.

Kann mit nur einem Platzhalter arbeiten *

Gilles Quenot
quelle
0
if ls -d $glob > /dev/null 2>&1; then
  echo Found.
else
  echo Not found.
fi

Beachten Sie, dass dies sehr zeitaufwändig sein kann, wenn viele Übereinstimmungen vorliegen oder der Dateizugriff langsam ist.

Florian Diesch
quelle
1
Dies gibt die falsche Antwort, wenn ein Muster wie verwendet [a]wird, wenn die Datei [a]vorhanden ist und die Datei afehlt. Es wird "gefunden" angezeigt, obwohl die einzige Datei, mit der es übereinstimmen sollte a, nicht vorhanden ist.
Chris Johnsen
Diese Version sollte in einem normalen POSIX / bin / sh (ohne Bashismen) funktionieren, und für den Fall, dass ich sie brauche, hat der Glob sowieso keine Klammern und ich muss mich nicht um Fälle kümmern, die es sind schrecklich pathologisch. Aber ich denke, dass es keinen guten Weg gibt, um zu testen, ob Dateien mit einem Glob übereinstimmen.
Ken Bloom
0
#!/bin/bash
set nullglob
touch /tmp/foo1 /tmp/foo2 /tmp/foo3
FOUND=0
for FILE in /tmp/foo*
do
    FOUND=$((${FOUND} + 1))
done
if [ ${FOUND} -gt 0 ]; then
    echo "I found ${FOUND} matches"
else
    echo "No matches found"
fi
Peter Lyons
quelle
2
Diese Version schlägt fehl, wenn genau eine Datei übereinstimmt. Sie können jedoch den Klumpen FOUND = -1 vermeiden, indem Sie die nullglobShell-Option verwenden.
Ken Bloom
@ Ken: Hmm, ich würde keinen Kludge nennen nullglob. Das Vergleichen eines einzelnen Ergebnisses mit dem ursprünglichen Muster ist ein Problem (und neigt zu falschen Ergebnissen) nullglob.
Chris Johnsen
@ Chris: Ich denke du hast falsch verstanden. Ich habe keinen nullglobKludge genannt.
Ken Bloom
1
@ Ken: In der Tat habe ich falsch verstanden. Bitte entschuldigen Sie meine ungültige Kritik.
Chris Johnsen
-1
(ls glob* &>/dev/null && echo Files found) || echo No file found
Damodharan R.
quelle
5
Würde auch false zurückgeben, wenn Verzeichnisse übereinstimmen glob*und Sie beispielsweise nicht über die Schreibfunktion verfügen, um diese Verzeichnisse aufzulisten.
Stephane Chazelas
-1

ls | grep -q "glob.*"

Nicht die effizienteste Lösung (wenn sich eine Menge Dateien im Verzeichnis befinden, kann dies langsam sein), aber es ist einfach, leicht zu lesen und hat auch den Vorteil, dass reguläre Ausdrücke leistungsfähiger sind als einfache Bash-Glob-Muster.

jesjimher
quelle
-2
[ `ls glob* 2>/dev/null | head -n 1` ] && echo true
Otocan
quelle
1
Für eine bessere Antwort versuchen Sie, Ihrem Code eine Erklärung hinzuzufügen.
MR