Shell-Dateinamenmuster, das sich zu Punktdateien, aber nicht zu `..` erweitert?

13

Kürzlich hatte ich ein kleines Missgeschick, das durch ein Muschelmuster verursacht wurde, das sich auf unerwartete Weise ausdehnte. Ich wollte den Besitzer einer Reihe von Punktedateien im /rootVerzeichnis ändern , also tat ich es

chown -R root .*

Natürlich wurde das .*erweitert, ..was ein bisschen katastrophal war.

Ich weiß, dass bashdieses Verhalten durch Ändern einer Shell-Option geändert werden kann, aber mit den Standardeinstellungen gibt es ein Muster, das auf jede Punktdatei im Verzeichnis erweitert wird, aber nicht auf .und ..?

Ernest A
quelle

Antworten:

20

Bash, ksh und zsh haben bessere Lösungen, aber in dieser Antwort gehe ich von einer POSIX-Shell aus.

Das Muster .[!.]*stimmt mit allen Dateien überein, die mit einem Punkt beginnen, gefolgt von einem Nicht-Punkt-Zeichen. (Beachten Sie, dass [^.]einige Shells, aber nicht alle, die portable Syntax für die Zeichensatzkomplementierung in Platzhaltermustern unterstützen [!.].) Aus diesem Grund werden .und ..-Dateien ausgeschlossen, aber auch Dateien, die mit zwei Punkten beginnen. Das Muster ..?*verarbeitet Dateien, die mit zwei Punkten beginnen und nicht nur aus Punkten bestehen ...

chown -R root .[!.]* ..?*

Dies ist das klassische Musterset für alle Dateien:

* .[!.]* ..?*

Eine Einschränkung dieses Ansatzes ist, dass, wenn eines der Muster mit nichts übereinstimmt, es an den Befehl übergeben wird. Wenn Sie in einem Skript alle Dateien in einem Verzeichnis mit Ausnahme von .und vergleichen möchten .., gibt es mehrere Lösungen, die alle umständlich sind:

  • Verwenden Sie * .*diese Option, um alle Einträge aufzulisten .und und ..in einer Schleife auszuschließen . Eines oder beide der Muster stimmen möglicherweise nicht überein, daher muss die Schleife prüfen, ob jede Datei vorhanden ist. Sie haben die Möglichkeit, nach anderen Kriterien zu filtern. Entfernen -hSie beispielsweise den Test, wenn Sie baumelnde symbolische Links überspringen möchten.

    for x in * .*; do
      case $x in .|..) continue;; esac
      [ -e "$x" ] || [ -h "$x" ] || continue
      somecommand "$x"
    done
  • Eine komplexere Variante, bei der der Befehl nur einmal ausgeführt wird. Beachten Sie, dass die Positionsparameter überlastet sind (POSIX-Shells haben keine Arrays). Stellen Sie dies in eine separate Funktion, wenn dies ein Problem ist.

    set --
    for x in * .[!.]* ..?*; do
      case $x in .|..) continue;; esac
      [ -e "$x" ] || [ -h "$x" ] || continue
      set -- "$@" "$x"
    done
    somecommand "$@"
  • Verwenden Sie das * .[!.]* ..?*Triptychon. Auch hier können eines oder mehrere der Muster mit nichts übereinstimmen, daher müssen wir nach vorhandenen Dateien suchen (einschließlich baumelnder symbolischer Links).

    for x in * .[!.]* ..?*; do
      [ -e "$x" ] || [ -h "$x" ] || continue
      somecommand "$x"
    done
  • Verwenden Sie den * .[!.]* ..?*Tryptich und führen Sie den Befehl einmal pro Muster aus, jedoch nur, wenn er mit etwas übereinstimmt. Dadurch wird der Befehl nur einmal ausgeführt. Beachten Sie, dass die Positionsparameter überlastet sind (POSIX-Shells haben keine Arrays). Versetzen Sie dies in eine separate Funktion, wenn dies ein Problem darstellt.

    set -- *
    [ -e "$1" ] || [ -h "$1" ] || shift
    set -- .[!.]* "$@"
    [ -e "$1" ] || [ -h "$1" ] || shift
    set -- ..?* "$@"
    [ -e "$1" ] || [ -h "$1" ] || shift
    somecommand "$@"
  • Verwenden Sie find. Mit GNU oder BSD find ist es mit den Optionen -mindepthund einfach, eine Rekursion zu vermeiden -maxdepth. Mit POSIX find ist es etwas kniffliger, aber es ist möglich. Dieses Formular bietet den Vorteil, dass der Befehl problemlos ein einziges Mal anstelle von einmal pro Datei ausgeführt werden kann (dies ist jedoch nicht garantiert: Wenn der resultierende Befehl zu lang ist, wird er in mehreren Stapeln ausgeführt).

    find . -name . -o -exec somecommand {} + -o -type d -prune
Gilles 'SO - hör auf böse zu sein'
quelle
6

Dies funktioniert, wenn alle Dateinamen mindestens drei Zeichen enthalten (einschließlich des Punkts):

chown -R root .??*

Für eine robustere Lösung können Sie Folgendes verwenden find:

find . -maxdepth 1 -name '.*' -exec chown -R root {} \;

quelle