Wie mache ich Bash Glob zu einer Stringvariablen?

Systeminfo

Betriebssystem: OS X

Bash: GNU Bash, Version 3.2.57 (1) -release (x86_64-apple-darwin16)

Hintergrund

Ich möchte, dass die Zeitmaschine eine Reihe von Verzeichnissen und Dateien aus allen meinen git / nodejs-Projekten ausschließt. Meine Projektverzeichnisse befinden sich in ~/code/private/ und ~/code/public/ , also versuche ich es Verwenden Sie die Bash-Schleife, um die tmutil durchzuführen.

Problem

Kurzversion

Wenn ich eine berechnet Zeichenfolgenvariable k, wie mache ich es global in oder direkt vor einem for- Schleife:

i="~/code/public/*" j="*.launch" k=$i/$j # $k="~/code/public/*/*.launch" for i in $k # I need $k to glob here do echo $i done 

In der langen Version unten sehen Sie k=$i/$j. Daher kann ich die Zeichenfolge nicht fest codieren die for-Schleife.

Lange Version

#!/bin/bash exclude=" *.launch .classpath .sass-cache Thumbs.db bower_components build connect.lock coverage dist e2e/*.js e2e/*.map libpeerconnection.log node_modules npm-debug.log testem.log tmp typings " dirs=" ~/code/private/* ~/code/public/* " for i in $dirs do for j in $exclude do k=$i/$j # It is correct up to this line for l in $k # I need it glob here do echo $l # Command I want to execute # tmutil addexclusion $l done done done 

Ausgabe

Sie sind nicht globbed. Nicht das, was ich will.

~/code/private/*/*.launch ~/code/private/*/.DS_Store ~/code/private/*/.classpath ~/code/private/*/.sass-cache ~/code/private/*/.settings ~/code/private/*/Thumbs.db ~/code/private/*/bower_components ~/code/private/*/build ~/code/private/*/connect.lock ~/code/private/*/coverage ~/code/private/*/dist ~/code/private/*/e2e/*.js ~/code/private/*/e2e/*.map ~/code/private/*/libpeerconnection.log ~/code/private/*/node_modules ~/code/private/*/npm-debug.log ~/code/private/*/testem.log ~/code/private/*/tmp ~/code/private/*/typings ~/code/public/*/*.launch ~/code/public/*/.DS_Store ~/code/public/*/.classpath ~/code/public/*/.sass-cache ~/code/public/*/.settings ~/code/public/*/Thumbs.db ~/code/public/*/bower_components ~/code/public/*/build ~/code/public/*/connect.lock ~/code/public/*/coverage ~/code/public/*/dist ~/code/public/*/e2e/*.js ~/code/public/*/e2e/*.map ~/code/public/*/libpeerconnection.log ~/code/public/*/node_modules ~/code/public/*/npm-debug.log ~/code/public/*/testem.log ~/code/public/*/tmp ~/code/public/*/typings 

Kommentare

  • Einfache Anführungszeichen stoppen die Shell-Interpolation in Bash, daher können Sie versuchen, Ihre Variable in doppelte Anführungszeichen zu setzen.
  • @ThomasN nein, das funktioniert nicht. k ist eine berechnete Zeichenfolge, und ich brauche sie sta y so bis zur Schleife. Bitte überprüfen Sie meine lange Version.
  • @ThomasN Ich habe die kurze Version aktualisiert, um sie klarer zu machen.

Antwort

Sie können eine weitere Bewertungsrunde mit eval erzwingen, aber das ist eigentlich nicht erforderlich. (Und eval startet Sie haben ernsthafte Probleme, sobald Ihre Dateinamen Sonderzeichen wie $ enthalten.) Das Problem liegt nicht beim Globbing, sondern bei der Tilde-Erweiterung.

Globbing tritt auf nach Variablenerweiterung, wenn die Variable nicht in Anführungszeichen gesetzt ist, wie hier (*) :

$ x="/tm*" ; echo $x /tmp 

Also, In der gleichen Weise ähnelt dies dem, was Sie getan haben, und funktioniert:

$ mkdir -p ~/public/foo/ ; touch ~/public/foo/x.launch $ i="$HOME/public/*"; j="*.launch"; k="$i/$j" $ echo $k /home/foo/public/foo/x.launch 

Aber mit der Tilde funktioniert es nicht „t:

$ i="~/public/*"; j="*.launch"; k="$i/$j" $ echo $k ~/public/*/*.launch 

Dies ist klar dokumentiert für Bash:

Die Reihenfolge der Erweiterungen lautet: Klammererweiterung; Tildeerweiterung, Parameter- und Variablenerweiterung, …

Die Tilde-Erweiterung erfolgt vor der Variablenerweiterung, sodass Tildes innerhalb von Variablen nicht erweitert werden. Die einfache Problemumgehung besteht darin, stattdessen $HOME oder den vollständigen Pfad zu verwenden.

(* Das Erweitern von Globs aus Variablen ist normalerweise nicht das, was Sie möchten)


Eine andere Sache:

Wenn Sie die Muster wie hier durchlaufen:

exclude="foo *bar" for j in $exclude ; do ... 

Beachten Sie, dass $exclude nicht in Anführungszeichen gesetzt ist, an dieser Stelle sowohl geteilt als auch globiert wird. Wenn das aktuelle Verzeichnis also etwas enthält, das dem Muster entspricht, wird es wie folgt erweitert:

$ i="$HOME/public/foo" $ exclude="*.launch" $ touch $i/real.launch $ for j in $exclude ; do # split and glob, no match echo "$i"/$j ; done /home/foo/public/foo/real.launch $ touch ./hello.launch $ for j in $exclude ; do # split and glob, matches in current dir! echo "$i"/$j ; done /home/foo/public/foo/hello.launch # not the expected result 

Um dies zu umgehen, verwenden Sie eine Array-Variable anstelle einer geteilten Zeichenfolge:

$ exclude=("*.launch") $ exclude+=("something else") $ for j in "${exclude[@]}" ; do echo "$i"/$j ; done /home/foo/public/foo/real.launch /home/foo/public/foo/something else 

Als zusätzlichen Bonus können Array-Einträge auch Leerzeichen ohne Probleme beim Teilen enthalten.


Ähnliches könnte getan werden find -path, wenn Sie nicht wissen, auf welcher Verzeichnisebene die Zieldateien sein sollen. Suchen Sie beispielsweise nach einem Pfad, der auf /e2e/*.js endet:

$ dirs="$HOME/public $HOME/private" $ pattern="*/e2e/*.js" $ find $dirs -path "$pattern" /home/foo/public/one/two/three/e2e/asdf.js 

Wir müssen $HOME anstelle von ~ aus demselben Grund wie zuvor, und $dirs muss auf dem find Befehlszeile, damit es geteilt wird, aber $pattern sollte in Anführungszeichen gesetzt werden, damit es nicht versehentlich von der Shell erweitert wird.

(Ich denke, Sie könnten mit -maxdepth auf GNU find spielen, um zu begrenzen, wie tief die Suche geht, wenn Sie sich interessieren, aber das ist ein etwas anderes Problem.)

Kommentare

  • Sind Sie die einzige Antwort mit find? Ich erkunde diese Route auch, da die for-Schleife immer komplizierter wird. Ich habe jedoch Probleme mit dem ‚ -Pfad ‚.
  • Gutschrift als Ihre Information über Tilde ‚ ~ ‚ bezieht sich direkter auf das Hauptproblem. Ich werde das endgültige Skript und die Erklärung in einer anderen Antwort veröffentlichen. Aber volle Anerkennung für Sie: D
  • @JohnSiu, ja, die Verwendung von find war das, woran Sie zuerst gedacht haben. Je nach Bedarf kann es auch verwendet werden. (oder besser auch für einige Zwecke.)
  • @kevinarpe, ich denke, Arrays sind im Grunde genommen genau dafür gedacht, und ja, "${array[@]}" (mit den Anführungszeichen! ) wird dokumentiert (siehe hier und hier ), um die Elemente als eindeutige Wörter ohne zu erweitern sie weiter aufteilen.
  • @sixtyfive, na ja, [abc] ist ein Standardteil von Glob-Mustern wie ?, ich ‚ denke nicht, dass es ‚ notwendig ist, alle hier abzudecken .

Antwort

Sie können es als Array anstelle einer Zeichenfolge speichern, die später in vielen Fällen verwendet werden soll Lassen Sie das Globbing geschehen, wenn Sie es definieren. In Ihrem Fall zum Beispiel:

 k=(~/code/public/*/*.launch) for i in "${k[@]}"; do  

oder im späteren Beispiel Sie „Ich muss eval einige der Zeichenfolgen

 dirs=(~/code/private/* ~/code/public/*) for i in "${dirs[@]}"; do for j in $exclude; do eval "for k in $i/$j; do tmutil addexclusion \"\$k\"; done" done done  

Kommentare

  • Beachten Sie, wie $exclude Platzhalter enthält, Sie ‚ d muss das Globbing deaktivieren, bevor der Operator split + glob verwendet wird, und es für die Verwendung $i/$j und not wiederherstellen eval aber verwenden Sie "$i"/$j
  • Sowohl Sie als auch ilkkachu geben eine gute Antwort. Seine Antwort identifizierte jedoch das Problem an ihn.

Antwort

@ilkkachu Antwort löste das Hauptproblem der Globbing. Volle Anerkennung für ihn.

V1

Aufgrund von exclude, die Einträge mit und ohne Platzhalter (*) enthalten, sind sie möglicherweise auch nicht vorhanden Insgesamt ist nach dem Globbing von $i/$j eine zusätzliche Überprüfung erforderlich. Ich teile meine Ergebnisse hier.

#!/bin/bash exclude=" *.launch .DS_Store .classpath .sass-cache .settings Thumbs.db bower_components build connect.lock coverage dist e2e/*.js e2e/*.map libpeerconnection.log node_modules npm-debug.log testem.log tmp typings " dirs=" $HOME/code/private/* $HOME/code/public/* " # loop $dirs for i in $dirs; do for j in $exclude ; do for k in $i/$j; do echo -e "$k" if [ -f $k ] || [ -d $k ] ; then # Only execute command if dir/file exist echo -e "\t^^^ Above file/dir exist! ^^^" fi done done done 

Erklärung der Ausgabe

Es folgt die Teilausgabe zur Erläuterung der Situation.

/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/a.launch ^^^ Above file/dir exist! ^^^ /Volumes/HD2/JS/code/public/simple-api-example-ng2-express/b.launch ^^^ Above file/dir exist! ^^^ /Volumes/HD2/JS/code/public/simple-api-example-ng2-express/.DS_Store ^^^ Above file/dir exist! ^^^ 

Die obigen Angaben sind selbsterklärend.

/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/.classpath /Volumes/HD2/JS/code/public/simple-api-example-ng2-express/.sass-cache /Volumes/HD2/JS/code/public/simple-api-example-ng2-express/.settings /Volumes/HD2/JS/code/public/simple-api-example-ng2-express/Thumbs.db /Volumes/HD2/JS/code/public/simple-api-example-ng2-express/bower_components /Volumes/HD2/JS/code/public/simple-api-example-ng2-express/build /Volumes/HD2/JS/code/public/simple-api-example-ng2-express/connect.lock /Volumes/HD2/JS/code/public/simple-api-example-ng2-express/coverage /Volumes/HD2/JS/code/public/simple-api-example-ng2-express/dist 

Die obigen Angaben werden angezeigt, weil der Ausschlusseintrag ($j) hat keinen Platzhalter, $i/$j wird zu einer einfachen Zeichenfolgenverkettung. Die Datei / das Verzeichnis ist jedoch nicht vorhanden.

/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/e2e/*.js /Volumes/HD2/JS/code/public/simple-api-example-ng2-express/e2e/*.map 

Die obigen Angaben werden als Ausschlusseintrag ($j) angezeigt Platzhalter, aber keine Datei- / Verzeichnisübereinstimmung. Beim Globbing von $i/$j wird nur die ursprüngliche Zeichenfolge zurückgegeben.

V2

V2 Verwenden Sie ein einfaches Anführungszeichen. eval und shopt -s nullglob, um ein sauberes Ergebnis zu erhalten. Es ist keine endgültige Überprüfung der Datei / des Verzeichnisses erforderlich.

#!/bin/bash exclude=" *.launch .sass-cache Thumbs.db bower_components build connect.lock coverage dist e2e/*.js e2e/*.map libpeerconnection.log node_modules npm-debug.log testem.log tmp typings " dirs=" $HOME/code/private/* $HOME/code/public/* " for i in $dirs; do for j in $exclude ; do shopt -s nullglob eval "k=$i/$j" for l in $k; do echo $l done shopt -u nullglob done done 

Kommentare

  • Ein Problem ist das in for j in $exclude, die Globs in $exclude könnten zum Zeitpunkt dieser $exclude -Erweiterung (und Aufruf) erweitert werden eval fragt nach Problemen). Sie ‚ möchten, dass das Globbing für for i in $dir und for l in $k aktiviert wird, jedoch nicht für for j in $exclude. Sie ‚ möchten eine set -f vor der letzteren und eine set +f für die andere. Im Allgemeinen möchten Sie ‚ Ihren split + glob-Operator optimieren, bevor Sie ihn verwenden. In jedem Fall möchten Sie ‚ nicht split + glob für echo $l, also $l sollte dort zitiert werden.
  • @St é phaneChazelas beziehen Sie sich auf v1 oder v2? Für v2 stehen sowohl exclude als auch dirs in einfachen Anführungszeichen (), so no globbing till eval`.
  • Globbing findet bei nicht zitierter Variable Expansion in Listenkontexten statt. Das ist (was eine Variable nicht zitiert), was wir tun Rufen Sie manchmal den Operator split + glob auf. ‚ gibt es kein Globbing bei Zuweisungen zu skalaren Variablen. foo=* und foo='*' ist dasselbe. echo $foo und echo "$foo" sind dies jedoch nicht (in Shells wie bash, es ‚ wurde in Muscheln wie zsh, fish oder rc behoben, siehe auch den obigen Link). Hier tun Sie möchte diesen Operator verwenden, aber an einigen Stellen nur den geteilten Teil und an anderen nur den Glob-Teil.
  • @St é phaneChazelas Vielen Dank für Die Info !!! Hat mich irgendwann gekostet, aber ich verstehe die Besorgnis jetzt. Das ist sehr wertvoll !! Danke !!!

Antwort

Mit zsh:

 exclude=" *.launch .classpath .sass-cache Thumbs.db ... " dirs=( ~/code/private/* ~/code/public/* ) for f ($^dirs/${^${=~exclude}}(N)) { echo $f }  

${^array}string soll als $array[1]string $array[2]string.... $=var führt eine Wortaufteilung für die Variable durch (etwas, das andere Shells standardmäßig tun!). $~var führt ein Globbing für die Variable durch (etwas anderes) Shells auch standardmäßig (wenn Sie dies im Allgemeinen nicht möchten, müssten Sie in anderen Shells $f oben zitieren)).

(N) ist ein Glob-Qualifizierer, der nullglob für jeden dieser Globs aktiviert, die sich aus diesem $^array1/$^array2 Erweiterung. Das führt dazu, dass sich die Globs zu nichts ausdehnen, wenn sie nicht übereinstimmen. Das macht auch einen Nicht-Glob wie ~/code/private/foo/Thumbs.db zu einem, was bedeutet, dass wenn dieser bestimmte nicht existiert, es ist nicht enthalten.

Kommentare

  • Das ist wirklich schön. Ich habe getestet und funktioniert. Es scheint jedoch, dass zsh empfindlicher auf Zeilenumbrüche reagiert, wenn Die Art und Weise, wie exclude eingeschlossen ist, wirkt sich auf die Ausgabe aus.
  • @JohnSiu, oh ja, Sie ‚ div Es scheint, dass split + glob und $^array in zwei separaten Schritten ausgeführt werden müssen, um sicherzustellen, dass die leeren Elemente verworfen werden (siehe Bearbeiten). Das sieht ein bisschen wie a aus Fehler in zsh, ich ‚ werde das Problem auf ihrer Mailingliste ansprechen.
  • Ich habe eine v2 für bash, das sauberer ist, aber immer noch nicht so kompakt wie Ihr zsh-Skript, lol

Antwort

Alt post aber ich bin hier so t gestolpert dachte, dies könnte anderen helfen 🙂

Es gibt eine kinda-glob () -Funktion in bash … sie heißt compgen -G. Sie gibt einen Wert pro Zeile aus und benötigt daher ein Readarray, um zu funktionieren.

Versuchen Sie Folgendes:

i="~/code/public/*" j="*.launch" k=$i/$j # $k="~/code/public/*/*.launch" readarray -t files < <(compgen -G "$k") # $k is globbed here for i in "${files[@]}" do echo "Found -$i-" done 

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.