Bash Globbing und Streitübergabe

8

Ich habe das folgende vereinfachte Bash-Skript

#!/bin/bash

files=("$@")

if [ "X$files" = "X" ]; then
  files=$HOME/print/*.pdf;
fi

for file in "${files[@]}"; do
  ls "$file";
done

Wenn ich Argumente (Dateinamen) als Parameter übergebe, gibt dieses Skript die richtigen Dateinamen aus. Wenn ich dagegen keine Argumente übergebe, wird es gedruckt

/home/user/print/*.pdf: No such file or directory

Warum werden die Dateinamen in diesem Fall nicht erweitert und wie kann ich das beheben? Beachten Sie, dass ich die Konstrukte files=("$@")und "${files[@]}"verwende, weil ich gelesen habe, dass sie den üblichen "files = $ *" vorzuziehen sind.

Highsciguy
quelle
Wo ist files=$*jemals üblich ? Das ist einfach falsch .
Stéphane Chazelas
Üblich ist relativ, richtig. Ich meinte eine Methode, die keine Arrays verwendet. Was würden Sie dann tun?
Highsciguy

Antworten:

10

Sie weisen fileseine skalare Variable anstelle einer Array- Variablen zu.

Im

 files=$HOME/print/*.pdf

Sie zuweisen einige Zeichenfolge wie /home/highsciguy/print/*.pdfzum $filesSkalar (aka string) variabel.

Verwenden:

files=(~/print/*.pdf)

oder

files=("$HOME"/print/*.pdf)

stattdessen. Die Shell erweitert dieses Globbing-Muster zu einer Liste von Dateipfaden und weist sie jeweils Elementen des $files Arrays zu .

Die Erweiterung des Globs erfolgt zum Zeitpunkt der Zuordnung.

Sie müssen keine nicht standardmäßigen sh-Funktionen verwenden, und Sie können die Systeme Ihres Systems shanstelle von bashhier verwenden, indem Sie sie schreiben:

#!/bin/sh -

[ "$#" -gt 0 ] || set -- ~/print/*.pdf

for file do
  ls -d -- "$file"
done

setist das "$@"Array der Positionsparameter zuzuweisen .

Ein anderer Ansatz könnte darin bestanden haben, das Globbing-Muster in einer skalaren Variablen zu speichern :

files=$HOME/print/*.pdf

Lassen Sie die Shell den Glob zum Zeitpunkt der $files Erweiterung der Variablen erweitern.

IFS= # disable word splitting
for file in $files; do ...

Da dies $filesnicht in Anführungszeichen steht (was Sie normalerweise nicht tun sollten), unterliegt seine Erweiterung der Wortaufteilung (die wir hier deaktiviert haben) und der Generierung von Globbing / Dateinamen.

Das *.pdf wird also auf die Liste der passenden Dateien erweitert. Wenn $HOMEsie jedoch Platzhalterzeichen enthalten, können sie auch erweitert werden, weshalb die Verwendung einer Arrayvariablen immer noch vorzuziehen ist.

Stéphane Chazelas
quelle
4

Möglicherweise haben Sie Dinge wie files=$*und files=~/print/*.pdfin älteren Shells ohne Arrays gesehen und dann ls $files.

Eine Variablensubstitution, die nicht in doppelten Anführungszeichen steht, interpretiert den Wert der Variablen als durch Leerzeichen getrennte Liste von Shell-Platzhaltermustern, die durch übereinstimmende Dateinamen ersetzt werden, falls vorhanden. Zum Beispiel, nach files=~/print/*.pdf, ls $fileserweitert um so etwas wie lsmit den Argumenten /home/highsciguy/print/bar.pdf, /home/highsciguy/print/foo.pdfusw. Im Fall files=$*verkettet diese Zuordnung die an das Skript übergebenen Argumente mit Leerzeichen dazwischen, und ls $filesspaltet sie wieder heraus.

All dies bricht zusammen, wenn Sie Dateinamen haben, die Leerzeichen oder Globbing-Zeichen enthalten. Deshalb sollten Sie dies nicht auf diese Weise tun. Verwenden Sie stattdessen Arrays.

files=("$@")
if ((${#files[@]} == 0)); then
  files=("$HOME"/print/*.pdf)
fi

Beachten Sie, dass

  • Alle Array-Zuweisungen erfordern Klammern um die Array-Werte : var=(…).
  • Überprüfen Sie die Länge eines Arrays, um zu testen, ob es leer ist. "$files"ist leer, wenn fileses sich um ein Array handelt, dessen Element des Index 0 nicht gesetzt ist, oder um eine leere Zeichenfolge. Dies [ "X$foo" = "X" ]ist auch eine veraltete Methode, um zu testen, ob sie $fooleer ist: Alle modernen Shells werden [ -n "$foo" ]korrekt implementiert . In Bash können Sie verwenden [[ -n $foo ]].

In Shells, die keine Arrays unterstützen, gibt es tatsächlich ein Array: die Positionsparameter für die Shell oder die aktuelle Funktion. Hier brauchen Sie das filesArray nicht wirklich , tatsächlich wäre es einfacher, die Positionsparameter zu verwenden.

#!/bin/sh
if [ "$#" -eq 0 ]; then
  set -- ~/print/*.pdf
fi
for file do 
Gilles 'SO - hör auf böse zu sein'
quelle