Was bedeutet „{} \;“ im Kontext des Befehls „find“?

Was bedeutet „{} \;“ im Kontext des Befehls „find“?

Ich möchte Dateien löschen, die haben size = 0. Also habe ich versucht:

find ./ -size 0 | xargs rm

Es besteht jedoch ein Problem mit Dateien, deren Namen mit einem Leerzeichen beginnen.

Bei meiner Suche im Internet habe ich Folgendes gefunden:

find ./ -size 0 -exec rm -i {} \;

Es funktioniert. Allerdings denke ich, dass meine Methode xargsdafür zu ausgefeilt ist.

Was {} \;bedeutet?

Kann mir das jemand erklären?

Mein Englisch ist nicht sehr gut, also schreiben Sie bitte einfach.

Antwort1

{}hat für absolut keine Bedeutung bashund wird daher unverändert als Argument an den hier ausgeführten Befehl übergeben find.

Andererseits ;hat es eine spezielle Bedeutung für bash. Es wird normalerweise verwendet, um aufeinanderfolgende Befehle zu trennen, wenn sie sich in derselben Befehlszeile befinden. Hier \;wird der Backslash in genau dazu verwendet, zu verhindern, dass das Semikolon von als Befehlstrennzeichen interpretiert wird, bashund es dann als Parameter an den zugrunde liegenden Befehl, , übergeben werden kann find. Das Semikolon in Anführungszeichen zu setzen, also ";"oder ';', hätte eine alternative Möglichkeit sein können, es unverarbeitet zu lassen.

Der Befehl:

find ./ -size 0 -exec rm -i {} \;

bedeutet: Suchen Sie im aktuellen Verzeichnis (beachten Sie, dass /hier nutzlos ist, .es kann sowieso nur ein Verzeichnis sein) nach allem, was die Größe 0 hat, und führen Sie für jedes gefundene Objekt den Befehl aus, d. h., rm -i nameSie werden interaktiv für jede Datei gefragt, ob Sie sie entfernen möchten. {}wird durch jeden Dateinamen ersetzt, der im ausgeführten Befehl gefunden wurde. Ein nettes Feature ist, dass dieser Dateiname strikt aus einem einzigen Argument besteht, unabhängig vom Dateinamen (auch wenn er eingebettete Leerzeichen, Tabulatoren, Zeilenvorschübe und andere Zeichen enthält). Dies wäre bei nicht der Fall xargs, sofern nicht nicht portable Hacks verwendet werden. Das finale ;ist da, um die -execKlausel zu beenden. Der Grund, warum sein Ende abgegrenzt werden muss, besteht darin, dass auf finddiese Option andere Optionen folgen könnten -exec, obwohl dies selten gemacht wird. Beispiel:

find . -name "*.js" -exec ls -l {} \; -name "special*" -exec wc -l {} \;

Ein Problem mit diesem Befehl ist, dass er keine nicht-normalen Dateien ignoriert und den Benutzer daher möglicherweise auffordert, Sockets, Block- und Zeichengeräte, Pipes und Verzeichnisse zu löschen. Bei Letzterem schlägt der Befehl immer fehl, auch wenn Sie mit „Ja“ antworten.

Ein weiteres Problem, das hier allerdings nicht wirklich kritisch ist, ist, dass rmfür jede Datei mit der Größe Null aufgerufen wird. Wenn Sie die -execEndung von /;durch ersetzen +, findwird die Erstellung des Unterprozesses optimiert, indem nur rmdie minimal mögliche Anzahl von Malen aufgerufen wird, häufig nur einmal.

So würde ich diesen Befehl ändern:

find . -type f -size 0 -exec rm -i {} +

Antwort2

Bei Verwendung find -execwird {}zu jedem gefundenen Ergebnis erweitert.

Wenn Sie beispielsweise ein Verzeichnis examplemit drei Dateien haben a.txt, wird , b.txtund wie folgt erweitert:c.txtfind example/ -exec rm -i {} \;

find example/ -exec rm -i example/a.txt \;
find example/ -exec rm -i example/b.txt \;
find example/ -exec rm -i example/c.txt \;

Das \;am Ende ist einfach ein Escapezeichen, ;um das Ende des Exec-Musters anzuzeigen. Andernfalls würde es von der Shell selbst interpretiert werden.

Antwort3

In Verbindung mit der Option finddes Befehls wird der Teil durch den Namen der Dateien ersetzt, die bei der Ausführung des Befehls gefunden werden. Das ist ebenfalls wichtig, da es das Ende des ausgeführten Befehls definiert.exec{}\;

Zum Beispiel

find ~ -name \*.bak -exec -rm -f {} \;

löscht alle Dateien mit der Endung „ .bakirgendwo“ im Home-Verzeichnis des Benutzers oder in darin enthaltenen Ordnern. Durch Ausführung rm -ffür jede gefundene Datei.

xargsnimmt Standardeingabezeilen, normalerweise aus einer Pipe, und bildet den Endteil der Argumente daraus, wenn es den Befehl ausführt, den Sie ihm geben

Antwort4

Dies ist eine alte Frage, aber ich möchte noch einige Informationen hinzufügen:

find ./ -size 0 -exec rm -i {} \;

Im vorherigen Befehl \;ist dies ein maskiertes Semikolon. Dies verhindert, dass der Befehl von der Shell verarbeitet wird (was normalerweise ;Befehle trennen würde).

Das -execArgument interpretiert alles bis zu diesem maskierten Semikolon als Befehl \;(d. h. rm -i {}es handelt sich um einen inneren Befehl, der von ausgeführt wird find). Innerhalb des inneren Befehls {}stellt das die Parametererweiterung dar. In einfachem Englisch bedeutet es „füge den hier gefundenen Dateinamen ein“.

Wenn also die gefundenen Dateien „file-a.txt“ und „file-b.txt“ wären, findwürde rm -i file-a.txtes ausgeführt rm -i file-b.txt.

Ein Problem mit diesem Befehl ist, dass er keine nicht-einfachen Dateien ignoriert und den Benutzer daher möglicherweise auffordert, Sockets, Block- und Zeichengeräte, Pipes und Verzeichnisse zu löschen. Bei Letzterem schlägt der Befehl immer fehl, auch wenn Sie mit „Ja“ antworten (d. h. Verzeichnisse müssen rekursiv gelöscht werden).

Ein weiteres Problem, das hier allerdings nicht wirklich kritisch ist, ist, dass rmfür jede Datei mit der Größe Null aufgerufen wird. Wenn Sie die -execEndung von /;durch ersetzen +, optimiert find die Erstellung des Unterprozesses, indem es nur rmdie minimal mögliche Anzahl von Malen, häufig nur einmal, aufgerufen wird.

So würde ich diesen Befehl ändern:

find ./ -type f -size 0 -exec rm -i {} +

curly bracketsoder braces: {}kann auf verschiedene Weise verwendet werden

Klammererweiterung

Klammern können zum Bilden von Sequenzen verwendet werden:

### prints out the numbers from 0 to 10
echo {0..10}

## prints out the same numbers, but in reverse order
echo {10..0}

## prints every second number, from 10 to 0
echo {10..0..2}

## prints every second letter, from z and working its way backwards to a.
echo {z..a..2}

Wir können auch zwei oder mehr Sequenzen kombinieren:

## prints out a pair of letters, from aa to zz.
echo {a..z}{a..z}

Präfixe und Suffixe hinzufügen:

### adds '"' as prefix and suffix
echo \"{These,words,are,quoted}\"
# output: "These" "words" "are" "quoted"

# concatenates the files file1, file2, and file3 into combined_file.
cat {file1,file2,file3} > combined_file

# copies "file22.txt" to "file22.backup"
cp file22.{txt,backup}

Notiz:

Innerhalb der Klammern sind keine Leerzeichen erlaubt, {...}es sei denn, die Leerzeichen sindzitiertoderentkam.

echo {file1,file2}\ :{\ A," B",' C'}
# output: file1 : A file1 : B file1 : C file2 : A file2 : B file2 : C

Erweiterte Klammererweiterung.

Klammern können zum Erstellen von Arrays verwendet werden. Arrays in Bash werden definiert, indem Elemente in Klammern gesetzt ()und jedes Element durch ein Leerzeichen getrennt wird, wie folgt:

month=("Jan" "Feb" "Mar" "Apr" "May" "Jun" "Jul" "Aug" "Sep" "Oct" "Nov" "Dec")

Um auf ein Element innerhalb des Arrays zuzugreifen, verwenden Sie seinen Index in Klammern []:

 # Array indexes start at [0], so [3] points to the fourth item
$ echo ${month[3]}
## output: Apr

Daher können wir ein Array mit etwa folgendem erstellen:

## builds an array that contains all the 2-letter combinations of the entire alphabet.
letter_combos=({a..z}{a..z})

## contains all the binary numbers for an 8-bit register, in ascending order,
## from 00000000, 00000001, 00000010, etc., to 11111111. 
dec2bin=({0..1}{0..1}{0..1}{0..1}{0..1}{0..1}{0..1}{0..1})

Letzteres ist besonders interessant, weil wir nun mit dec2bin einen 8-Bit-Dezimal-zu-Binär-Konverter erstellen können. Angenommen, Sie möchten wissen, was 25 im Binärformat ist. Sie können Folgendes tun:

$ echo ${dec2bin[25]}
## output: 00011001

Aber Teo, gibt es keine besseren Möglichkeiten, Dezimalzahlen in Binärzahlen umzuwandeln?

  • Ja, das gibt es, aber es ist trotzdem interessant, oder?

Gruppierungsbefehle

{ ... }kann verwendet werden, um eine Liste von Befehlen zu platzieren, die im aktuellen Shell-Kontext ausgeführt werden sollen. NeinUnterschalewird erstellt. Das Semikolon ;(oder Newline) nach der Liste isterforderlich.

Klammern ()dienen zum Ausführen von Befehlen in einerUnterschale:

menu_type=bar
echo $menu_type
## output: bar

## new lets called in a sub-shell
(menu_type=superbar; echo $menu_type)
## output: superbar

## back to the context
echo $menu_type
## output: bar

superbarWir können nicht auf den neuen Wert von zugreifen menu_type.

Wenn wir jedoch so etwas ausführen:

{ menu_type=superbar; echo $menu_type; }
## output: superbar

echo $menu_type
## output: superbar

{ ... }erstellt keine Unter-Shell, deshalb können wir auf den menu_typeWert zugreifen.

{ ... }werden auch als bezeichnet inline group. Tatsächlich wird damit eine anonyme Funktion erstellt (d. h. eine Funktion ohne Namen). Anders als bei einer „Standardfunktion“ { ... }bleiben die Variablen innerhalb von für den Rest des Skripts sichtbar.

Kann auch { ... }verwendet werden, um die Ausgabe mehrerer Befehle in zu gruppieren stdoutoder eine Umleitung dorthin zu erhalten stdin. Sehen wir uns ein Beispiel an:

#!/bin/bash
# rpm-check.sh
#  Queries an rpm file for description, listing, and whether it can be installed.
#  Saves output to a file.

SUCCESS=0
E_NOARGS=65

if [ -z "$1" ]; then
  echo "Usage: `basename $0` rpm-file"
  exit $E_NOARGS
fi  

{ # Begin command group.
  echo
  echo "Archive Description:"
  rpm -qpi $1       # Query description.
  echo
  echo "Archive Listing:"
  rpm -qpl $1       # Query listing.
  echo
  rpm -i --test $1  # Query whether rpm file can be installed.
  if [ "$?" -eq $SUCCESS ]
  then
    echo "$1 can be installed."
  else
    echo "$1 cannot be installed."
  fi  
  echo              # End command group.
} > "$1.test"       # Redirects output of everything in block to file.

echo "Results of rpm test in file $1.test"

exit 0

Sehen wir uns nun an, wie Sie mit einer E/A-Umleitung in der Gruppe fortfahren stdin:

#!/bin/bash
File=/etc/fstab

## reads the first two lines of the file
{
  read line1
  read line2
} < $File

echo "First line in $File is:"
echo "$line1"
echo
echo "Second line in $File is:"
echo "$line2"

exit 0

Ein weiteres Beispiel für das Speichern der Ausgabe einer Befehlsgruppe in einer Datei

## exec commands sequentially and redirects the output of the ls command into the png-list.txt file
echo "I found all these png files:"; find . -iname "*.png"; echo "Within this bunch of files:"; ls > png-list.txt

## exec commands sequentially and redirects the output of the group into the png-list.txt file
{ echo "I found all these png files:"; find . -iname "*.png"; echo "Within this bunch of files:"; ls; } > png-list.txt

Was ist der Unterschied, Teo?

Also, junger Padawan. Der Zweite erstellt die Datei png-list.txtmit allen Ausgaben, beginnend mit der Zeile “I found all these png files:“und endend mit der lsBefehlsausgabe.

Sub-Shell-Hack

Bash erstellt eine Unter-Shell für einen { ... }Klammergruppenbefehldann und nur dann, wennes ist Teil einer Pipeline, zum Beispiel:

$ { A=1; { A=2; sleep 2; } ; echo $A; }
## output: 2

$ { A=1; { A=2; sleep 2; } | sleep 1; echo $A; }
## output: 1

Notiz:

Zwischen den Klammern und der darin eingeschlossenen Befehlsliste befindet sich ein Leerzeichen. Das liegt daran, dass {und }reservierte Wörter sind (d. h. in die Shell integrierte Befehle). Außerdem muss die Befehlsliste mit einem Semikolon enden ;oder Zeilenumbrüche zum Trennen der Befehle verwenden.

Parametererweiterung

Okay, zurück zu

month=("Jan" "Feb" "Mar" "Apr" "May" "Jun" "Jul" "Aug" "Sep" "Oct" "Nov" "Dec")
echo ${month[3]}
## output: Apr

Hier werden die Klammern {}nicht als Teil eines Sequenzgenerators verwendet, sondern als Möglichkeit, Parametererweiterungen zu generieren. Bei der Parametererweiterung geht es um das, was auf der Verpackung steht:

Es nimmt die Variable oder den Ausdruck innerhalb der Klammern und erweitert ihn zu dem, was er darstellt.

Was bedeutet das, Teo?

Es bedeutet, ${...}dass die Shell angewiesen wird, alles zu expandieren, was sich darin befindet. In diesem Fall monthist es das Array, das wir zuvor definiert haben, also:

month=("Jan" "Feb" "Mar" "Apr" "May" "Jun" "Jul" "Aug" "Sep" "Oct" "Nov" "Dec")

Und das Element 3innerhalb des Arrays zeigt auf "Apr"(d. h. der erste Index in einem Array in Bash ist [0]). Das bedeutet echo ${month[3]}, dass nach der Erweiterung in echo übersetzt wird "Apr".

Eine Variable als ihren Wert zu interpretieren ist eine Möglichkeit, sie zu erweitern, aber es gibt noch ein paar weitere Möglichkeiten, die wir nutzen können. Wir können die Parametererweiterung verwenden, um zu manipulieren, was Sie aus der Variable lesen (z. B. indem wir am Ende einen Teil abschneiden).

Angenommen, Sie haben eine Variable wie:

a="This sentence is too longgg"

## chops off the last two gs
echo ${a%gg}
## output: This sentence is too long

Dies kann nützlich sein, um Dateien von einem Format in ein anderes zu konvertieren. Wenn wir beispielsweise einen Befehl haben, der ein JPEG-Bild namens nimmt image.jpgund in ein PNG-Bild namens konvertiert image.png:

convert image.jpg image.png

Wir können es folgendermaßen umschreiben:

i='image.jpg'
## chops off the extension 'jpg' and adds 'png'
convert $i "${i%jpg}png"
## output: convert image.jpg image.png

Aber wie kann das nützlicher sein, als einfach den Namen der Datei einzuschreiben?

Wenn wir also ein Verzeichnis mit Hunderten von JPEG-Bildern haben, die Sie in PNG konvertieren müssen, führen Sie einfach Folgendes darin aus:

for i in *.jpg; do convert $i ${i%jpg}png; done

… und alle Bilder werden automatisch konvertiert. Gern geschehen, junger Padawan.

Wenn Sie einen Teil vom Anfang einer Variablen abschneiden müssen, %verwenden Sie statt #:

$ a="Hello World!"

## chops off the word 'Hello' and adds 'Goodbye'
$ echo Goodbye${a#Hello}
## output: Goodbye World!

Platzhalter für Text

Wird danach verwendet xargs -i(d. h. nach der Option zum Ersetzen von Zeichenfolgen). Die {}doppelten geschweiften Klammern sind ein Platzhalter für den Ausgabetext.

## Execute 'echo ./<file>' for each file in the directory
ls . | xargs -i -t echo ./{} $1
#            ^^         ^^

Pfadname

Ein Pfadname ist ein Dateiname, der den vollständigen Pfad enthält. Beispiel: /home/<user>/Notes/todo.txt. Dies wird manchmal als absoluter Pfad bezeichnet. Wir werden {}hauptsächlich in findKonstrukten darauf stoßen, die enthalten -exec <command> \;. Dies ist jedoch kein Shell-Built-in. Wenn <command>enthält {}, ersetzt find den vollständigen Pfadnamen der ausgewählten Datei durch "{}".

# Removes all core dump files from user's home directory.
find ~/ -name 'core*' -exec rm {} \;

verwandte Informationen