Wie übergebe ich den Platzhalter '*' über eine Variable im Skript an den Pfadparameter des Befehls find?

9

Ich möchte verwenden find, um Dateien in einer Reihe von Ordnern zu finden, die durch Platzhalter eingeschränkt sind, aber im Pfadnamen Leerzeichen enthalten.

Über die Befehlszeile ist dies einfach. Die folgenden Beispiele funktionieren alle.

find  te*/my\ files/more   -print
find  te*/'my files'/more  -print
find  te*/my' 'files/more  -print

Diese finden beispielsweise Dateien in terminal/my files/moreund tepid/my files/more.

Ich brauche dies jedoch, um Teil eines Skripts zu sein. Was ich brauche ist so etwas:

SEARCH='te*/my\ files/more'
find ${SEARCH} -print

Leider kann ich, was auch immer ich tue, keine Platzhalter und Leerzeichen in einem findBefehl innerhalb eines Skripts mischen . Das obige Beispiel gibt die folgenden Fehler zurück (beachten Sie die unerwartete Verdoppelung des Backslashs):

find: te*/my\\’: No such file or directory
find: files/more’: No such file or directory

Der Versuch, Anführungszeichen zu verwenden, schlägt ebenfalls fehl.

SEARCH="te*/'my files'/more"
find ${SEARCH} -print

Dies gibt die folgenden Fehler zurück, nachdem die Bedeutung der Anführungszeichen ignoriert wurde:

find: te*/'my’: No such file or directory
find: ‘files'/more’: No such file or directory

Hier ist noch ein Beispiel.

SEARCH='te*/my files/more'
find ${SEARCH} -print

Wie erwartet:

find: te*/my’: No such file or directory
find: files/more’: No such file or directory

Jede Variation, die ich ausprobiert habe, gibt einen Fehler zurück.

Ich habe eine Problemumgehung, die möglicherweise gefährlich ist, weil zu viele Ordner zurückgegeben werden. Ich konvertiere alle Leerzeichen wie folgt in ein Fragezeichen (einstelliges Platzhalterzeichen):

SEARCH='te*/my files/more'
SEARCH=${SEARCH// /?}       # Convert every space to a question mark.
find ${SEARCH} -print

Dies entspricht:

find te*/my?files/more -print

Dies gibt nicht nur die richtigen Ordner zurück, sondern auch die terse/myxfiles/more, die es nicht soll.

Wie kann ich erreichen, was ich versuche? Google hat mir nicht geholfen :(

Paddy Landau
quelle
@ KasiyA Ich benutze Bash; Sie müssen etwas anderes verwenden, da ich diese Konstruktion noch nie gesehen habe. Der Befehl führt dazu, dass SEARCH: command not foundder Befehl find -printausgeführt wird.
Paddy Landau
Ein Schuss in die Dunkelheit, aber was ist mit Zitieren? find "${SEARCH}" -print?
Alaa Ali
@AlaaAli Nein, es funktioniert nicht, da das Zitieren verhindert, dass Bash die Platzhalter verwendet. Es wird nach einem Pfad speziell mit dem Namen gesucht (in meinem Beispiel) te*/'my files'/more.
Paddy Landau

Antworten:

8

Der exakt gleiche Befehl sollte in einem Skript gut funktionieren:

#!/usr/bin/env bash
find  te*/my\ files/ -print

Wenn Sie es als Variable benötigen , wird es etwas komplexer:

#!/usr/bin/env bash
search='te*/my\ files/'
eval find "$search" -print

WARNUNG:

Eine solche Verwendung evalist nicht sicher und kann zur Ausführung von beliebigem und möglicherweise schädlichem Code führen, wenn Ihre Dateinamen bestimmte Zeichen enthalten können. Siehe Bash-FAQ 48 Informationen finden .

Es ist besser, den Pfad als Argument zu übergeben:

#!/usr/bin/env bash
find "$@" -name "file*"

Ein anderer Ansatz besteht darin, finddie erweiterten Globbing-Funktionen und Globs von bash vollständig zu vermeiden und zu verwenden:

#!/usr/bin/env bash
shopt -s globstar
for file in te*/my\ files/**; do echo "$file"; done

Mit der globstarBash-Option können Sie **rekursiv übereinstimmen:

globstar
      If set, the pattern ** used in a pathname expansion con
      text will match all files and zero or  more  directories
      and  subdirectories.  If the pattern is followed by a /,
      only directories and subdirectories match.

Verwenden Sie diese Option, damit es sich zu 100% wie das Suchen und Einschließen von Punktdateien (versteckten Dateien) verhält

#!/usr/bin/env bash
shopt -s globstar
shopt -s dotglob
for file in te*/my\ files/**; do echo "$file"; done

Sie können sie sogar echodirekt ohne die Schleife:

echo te*/my\ files/**
Terdon
quelle
2
Ich habe dies als Antwort festgelegt, da Terdon hilfreiche Kommentare abgegeben hat (nicht zu vergessen die hilfreichen Kommentare anderer). Ich habe Bashs Globbing-Fähigkeit in der Befehlszeile verwendet, um mehrere Pfade an mein Skript zu übergeben, anstatt dass das Skript versucht, es zu sortieren. Es funktioniert gut.
Paddy Landau
2

Wie wäre es mit Arrays?

$ tree Desktop/ Documents/
Desktop/
└── my folder
    └── more
        └── file
Documents/
└── my folder
    ├── folder
    └── more

5 directories, 1 file
$ SEARCH=(D*/my\ folder)
$ find "${SEARCH[@]}" 
Desktop/my folder
Desktop/my folder/more
Desktop/my folder/more/file
Documents/my folder
Documents/my folder/more
Documents/my folder/folder

(*)Wird zu einem Array erweitert, das dem Platzhalter entspricht. Und "${SEARCH[@]}"erweitert sich in alle Elemente im Array ( [@]), wobei jedes einzeln in Anführungszeichen gesetzt wird.

Nachträglich ist mir klar, dass ich dazu in der Lage sein sollte. Etwas wie:

find . -path 'D*/my folder/more/'
muru
quelle
Kluge Idee, aber leider funktioniert es nicht. Warum? Weil der Pfad selbst in einer Variablen gehalten wird; daher INPUTPATH='te*/my files/moreund SEARCH=(${INPUTPATH}). Unabhängig davon, wie unterschiedlich ich dies mache, erhalte ich immer noch ein nicht funktionierendes Ergebnis. Das scheint unmöglich!
Paddy Landau
Dies ist natürlich alles wahr, aber das OP muss es in einem Skript tun. Das ändert die Dinge, da die Erweiterung von Platzhaltern erheblich komplexer wird und dies nicht funktioniert.
Terdon
@PaddyLandau in diesem Fall, warum können Sie nicht verwenden find‚s - -pathFilter? Es verwendet Platzhalter und muss definitiv nicht erweitert werden.
Muru
@muru Das ist interessant; Ich wusste nichts davon -path. In den letzten 10 Minuten habe ich jedoch die Antwort herausgefunden: Verwenden eval! Es scheint einfacher zu sein als -path.
Paddy Landau
2
@terdon und muru und alle: Danke. Ich habe gehört, was Sie alle gesagt haben, und mir wurde klar, dass ich mein Skript dazu bringen sollte, eine Sache zu tun und Bash Globbing mehrere Dateien oder Pfade zum Skript übergeben zu lassen. Ich habe mein Skript so geändert. Es funktioniert gut und passt besser zur Linux-Philosophie. Danke nochmal!
Paddy Landau
0

Ich habe endlich die Antwort herausgefunden.

Fügen Sie allen Leerzeichen einen Backslash hinzu:

SEARCH='te*/my files/more'
SEARCH=${SEARCH// /\\ }

An dieser Stelle SEARCHenthält te*/my\ files/more.

Dann verwenden eval.

eval find ${SEARCH} -print

So einfach ist das! Die Verwendung evalumgeht die Interpretation ${SEARCH}einer Variablen.

Paddy Landau
quelle
Bitte nicht. .
Terdon
@terdon Danke für die Warnung. Zurück zum Zeichenbrett!
Paddy Landau
Ja, das ist überraschend schwierig. Ich habe gerade meine Antwort mit einem anderen Ansatz aktualisiert. Warum nicht stattdessen Globbing verwenden? Wenn das immer noch nicht funktioniert, schlage ich vor, dass Sie unter Unix und Linux eine neue Frage stellen, in der erläutert wird, was Ihr Endziel ist und warum Sie das Muster als Variable benötigen. Es ist wahrscheinlicher, dass solche Dinge dort eine bessere Antwort erhalten.
Terdon