Prüfen Sie, ob Dateien vorhanden sind, die mit einem Muster übereinstimmen, um ein Skript auszuführen

29

Ich versuche, eine ifAnweisung zu schreiben, um zu testen, ob Dateien vorhanden sind, die einem bestimmten Muster entsprechen. Befindet sich eine Textdatei in einem Verzeichnis, sollte ein bestimmtes Skript ausgeführt werden.

Mein Code derzeit:

if [ -f /*.txt ]; then ./script fi

Bitte geben Sie einige Ideen; Ich möchte das Skript nur ausführen, wenn sich ein .txtim Verzeichnis befindet.

user40952
quelle
3
Sind Sie sicher, dass "das Verzeichnis" sein soll /? Außerdem fehlt Ihnen zuvor ein Semikolon fi.
Depquid
Die sauberste robuste Lösung, die mir begegnet ist, ist die Verwendung, findwie hier beim Stackoverflow erläutert .
Joshua Goldberg

Antworten:

39
[ -f /*.txt ]

würde nur dann true zurückgeben, wenn es eine (und nur eine) nicht ausgeblendete Datei gibt, /deren Name auf endet, .txtund wenn diese Datei eine reguläre Datei oder ein Symlink zu einer regulären Datei ist.

Das liegt daran, dass Platzhalter von der Shell erweitert werden, bevor sie an den Befehl übergeben werden (hier [).

Also , wenn es eine ist /a.txtund /b.txt, [wird 5 Argumente übergeben werden: [, -f, /a.txt, /b.txtund ]. [würde mich dann beschweren, dass -fzu viele argumente vorgebracht werden.

Wenn Sie überprüfen möchten, ob das *.txtMuster auf mindestens eine nicht ausgeblendete Datei erweitert wird (regulär oder nicht):

shopt -s nullglob
set -- *.txt
if [ "$#" -gt 0 ]; then
  ./script "$@" # call script with that list of files.
fi
# Or with bash arrays so you can keep the arguments:
files=( *.txt )
# apply C-style boolean on member count
(( ${#files[@]} )) && ./script "${files[@]}"

shopt -s nullglobist bashspezifisch, aber Muscheln mögen ksh93, zsh, yash, tcshgleichwertige Aussagen.

Beachten Sie, dass diese Dateien beim Lesen des Inhalts des Verzeichnisses gefunden werden. Es wird nicht versucht, auf diese Dateien zuzugreifen. Dies macht es effizienter als Lösungen, die Befehle wie lsoder statin dieser Liste von Dateien aufrufen , die von der Shell berechnet werden.

Das Standardäquivalent shwäre:

set -- [*].txt *.txt
case "$1$2" in
  ('[*].txt*.txt') ;;
  (*) shift; script "$@"
esac

Das Problem ist, dass bei Bourne- oder POSIX-Shells ein Muster, das nicht übereinstimmt, sich selbst erweitert. Wenn also *.txterweitert wird *.txt, wissen Sie nicht, ob es sich um eine .txtDatei handelt, die nicht im Verzeichnis enthalten ist, oder um eine Datei, die aufgerufen wird *.txt. Mit [*].txt *.txtkönnen Sie zwischen beiden unterscheiden.

Stéphane Chazelas
quelle
[ -f /*.txt ]ist ziemlich schnell im Vergleich zu compgen.
Daniel Böhmer
@ DanielBöhmer [ -f /*.txt ]wäre falsch, aber in meinem Test auf ein Verzeichnis, das 3425Dateien enthält , 94von denen nicht versteckte txt-Dateien sind, compgen -G "*.txt" > /dev/null 2>&1scheinen so schnell wie set -- *.txt; [ "$#" -gt 0 ](20,5 Sekunden für beide, wenn in meinem Fall 10000 Mal wiederholt).
Stéphane Chazelas
11

Sie könnten immer verwenden find:

find . -maxdepth 1 -type f -name "*.txt" 2>/dev/null | grep -q . && ./script

Erläuterung:

  • find . : Suche im aktuellen Verzeichnis
  • -maxdepth 1: Nicht in Unterverzeichnissen suchen
  • -type f : Nur normale Dateien durchsuchen
  • name "*.txt" : Suche nach Dateien mit der Endung .txt
  • 2>/dev/null : Fehlermeldungen umleiten an /dev/null
  • | grep -q . : grep für ein beliebiges Zeichen, gibt false zurück, wenn keine Zeichen gefunden wurden.
  • && ./script: ./scriptNur ausführen , wenn der vorherige Befehl erfolgreich war (&& )
terdon
quelle
2
findGibt nur false zurück, wenn beim Suchen nach Dateien Probleme auftreten, nicht, wenn keine Datei gefunden wird. Sie möchten die Ausgabe weiterleiten, um grep -q .zu überprüfen, ob etwas gefunden wird.
Stéphane Chazelas
@StephaneChazelas du hast natürlich ganz recht. Seltsam, ich hatte es getestet und es schien zu funktionieren. Muss etwas Seltsames getan haben, weil es nicht mehr geht. Wann wird "Probleme beim Finden von Dateien" gefunden?
terdon
@terdon, zum Beispiel, wenn auf ein Verzeichnis nicht zugegriffen werden kann, oder E / A-Fehler oder Fehler, die von einem Systemaufruf zurückgegeben werden. In diesem Fall versuchen Sie es nach chmod a-x ..
Stéphane Chazelas
8

Eine mögliche Lösung ist auch Bash Builtin compgen. Dieser Befehl gibt alle möglichen Übereinstimmungen für ein globales Muster zurück und verfügt über einen Beendigungscode, der angibt, ob Dateien übereinstimmen.

compgen -G "/*.text" > /dev/null && ./script

Ich habe diese Frage gefunden, als ich nach Lösungen gesucht habe, die allerdings schneller sind.

Daniel Böhmer
quelle
1
Guter Fund! Wenn Sie sich in einem Multi-Byte-Gebietsschema befinden, können Sie es mit geringfügig verbessern LC_ALL=C compgen -G "*.txt" > /dev/null.
Stéphane Chazelas
7

Hier ist ein Einzeiler, um es zu tun:

$ ls
file1.pl  file2.pl

Dateien existieren

$ stat -t *.pl >/dev/null 2>&1 && echo "file exists" || echo "file doesn't exist"
file exists

Dateien existieren nicht

$ stat -t -- *.txt >/dev/null 2>&1 && echo "file exists" || echo "file don't exist"
file don't exist

Dieser Ansatz nutzt die ||&& Operatoren und in bash verwendet. Dies sind die Operatoren "oder" und "und".

Wenn also der Befehl stat den Wert $?0 zurückgibt , dann den erstenecho aufgerufen. Wenn er den Wert 1 zurückgibt, wird der zweite echoaufgerufen.

Rückgabe ergibt sich aus stat

# a failure
$ stat -t -- *.txt >/dev/null 2>&1
$ echo "$?"
1

# a success
$ stat -t -- *.pl >/dev/null 2>&1
$ echo "$?"
0

Diese Frage wird beim Stackoverflow ausführlich behandelt:

slm
quelle
1
Warum die Nicht-Norm verwenden, statwenn ls -ddies auch möglich ist?
Stéphane Chazelas
Ich dachte ls -dliste ein verzeichnis auf? Schien nicht zu funktionieren, als ich ls -d *.plzum Beispiel versucht habe, ein Verzeichnis mit Dateien darin aufzulisten .
slm
Sie können die Anweisung links von &&durch ersetzen, ls *.txtund es wird auch funktionieren. /dev/nullStellen Sie sicher, dass Sie stdout und stderr an senden, wie von @slm vorgeschlagen.
Unxnut
1
Wenn Sie verwenden ls *.txtund es gibt keine Dateien innerhalb des Verzeichnisses wird dies eine Rückkehr $? = 2, das wird noch Arbeit mit dem , wenn dann, aber das war einer meiner Gründe für die Wahl statüber ls. Ich wollte eine 0 für den Erfolg und eine 1 für einen Misserfolg.
slm
ls -dist, Verzeichnisse anstelle ihres Inhalts aufzulisten. Also ls -dmacht das lstatauf der Datei genauso wie GNU stat. Welche Exit-Status-Befehle ungleich Null bei einem Fehler zurückkehren, ist systemspezifisch. Es macht wenig Sinn, Annahmen darüber zu treffen.
Stéphane Chazelas
4

Wie Chazelas betont, schlägt Ihr Skript fehl, wenn die Platzhaltererweiterung mehr als einer Datei entspricht.

Es gibt jedoch einen Trick, den ich benutze (auch wenn ich ihn nicht sehr mag ), um herumzukommen:

PATTERN=(/*.txt)
if [ -f ${PATTERN[0]} ]; then
...
fi

Wie es funktioniert?

Die Platzhaltererweiterung stimmt mit einer Reihe von Dateinamen überein, wir erhalten den ersten, wenn einige vorhanden sind, andernfalls null, wenn keine Übereinstimmung vorliegt.

Herr Pei
quelle
IMO das ist die am wenigsten schlechte Antwort hier. Sie scheinen alle ziemlich schrecklich zu sein, als würde ein grundlegendes Merkmal in der Sprache fehlen.
Plugwash
@plugwash es ist beabsichtigt ... * Nix-Shell-Skripte haben eine grundlegende Ablaufsteuerung und ein paar andere Kleinigkeiten, aber am Ende des Tages ist es Aufgabe, andere Befehle zusammenzufügen. Wenn bash saugt ... liegt es daran, dass die Befehle, die Sie verwenden, saugen
cb88
2
Das ist die falsche Logik (und es fehlen Anführungszeichen). Dadurch wird überprüft, ob die erste übereinstimmende Datei eine reguläre Datei ist. Möglicherweise handelt es sich um nicht reguläre Dateien, aber möglicherweise gibt es mehrere andere .txtDateien, die vom Typ regulär sind . Versuchen Sie es zum Beispiel danach mkdir a.txt; mkfifo b.txt; echo regular > c.txt.
Stéphane Chazelas
1

Einfach wie:

cnt=`ls \*.txt 2>/dev/null | wc -l`
if [ "$cnt" != "0" ]; then ./script fi

wc -l zählt die Zeilen im erweiterten Platzhalter.

Kim Johansson
quelle
1
Downvote: Dies sammelt eine erstaunliche Anzahl von Anfänger-Antimustern in einer kleinen Menge Code. Sie sollten die lsAusgabe nicht analysieren und fast nie $?direkt untersuchen, da dies ifbereits erfolgt. Auch die Verwendung von, um wczu sehen, ob etwas passiert ist, wird in ähnlicher Weise fehlgeleitet.
Tripleee
0

Ich mag die vorherige Array-Lösung, aber das könnte bei einer großen Anzahl von Dateien zu Verschwendung führen - die Shell würde viel Speicher zum Erstellen des Arrays verwenden und nur das erste Element würde jemals getestet werden.

Hier ist eine alternative Struktur, die ich gestern getestet habe:

$ cd /etc; if [[ $(echo * | grep passwd) ]];then echo yes;else echo no;fi yes $ cd /etc; if [[ $(echo * | grep password) ]];then echo yes;else echo no;fi no

Der Exit-Wert von grep scheint den Pfad durch die Kontrollstruktur zu bestimmen. Dies wird auch mit regulären Ausdrücken und nicht mit Shell-Mustern getestet. Einige meiner Systeme verfügen über den Befehl "pcregrep", der komplexere Regex-Übereinstimmungen ermöglicht.

(Ich habe diese Antwort bearbeitet, um ein "ls" in der Befehlsersetzung zu entfernen, nachdem ich die obige Kritik zum Parsen gelesen habe.)

Charlie
quelle
-2

Wenn Sie eine if-Klausel verwenden möchten, werten Sie die Anzahl aus:

if (( `ls *.txt 2> /dev/null|wc -l` ));then...
Rusty75
quelle