it-swarm.com.de

Wie verwende ich den Befehl exec in einer for-Schleife nach einem find-Befehl?

Ich habe versucht, dies für eine Weile zum Laufen zu bringen. Ich möchte nach einer Liste von Dateien suchen und sie dann alle auf einen bestimmten Pfad kopieren.

Wenn ich den Befehl separat ausführe, funktioniert das folgendermaßen:

find <path> -name 'file1' -exec cp '{}' <path2> \;

Aber ich konnte es nicht in einer for-Schleife ausführen.

#this works
for f in file1 file2 file3...; do find <path> -name "$f" \; done;
#none of these work (this code tries to find and copy the files named file and list from the path)
for f in file1 file2 file3...; do find <path> -name "$f" -exec cp '{}' <path2> \; done;
for f in file1 file2 file3...; do find <path> -name "$f" -exec cp {} <path2> \; done;

Ich habe ein paar andere Sachen ausprobiert, die wahrscheinlich nicht funktionieren würden. Die erste Zeile im Code-Zitat bleibt hängen und die anderen kopieren nichts, obwohl sie nicht hängen bleiben.

Ich habe nach einer Suche noch nichts mit exec in einer for-Schleife ausführen können, und jetzt bin ich mir nicht sicher, was ich tun soll.

Ich habe das unmittelbare Problem gelöst, indem ich die Dateien durchsucht und die Ergebnisse in einer anderen Datei protokolliert habe und dann eine Kopie in einer for-Schleife separat ausgeführt habe, aber ich möchte immer noch wissen, wie das geht.

4
John Hamilton

Während die anderen Antworten korrekt sind, möchte ich einen anderen Ansatz anbieten, bei dem nicht mehrere Aufrufe von find erforderlich sind, um dieselbe Verzeichnisstruktur wiederholt zu durchsuchen. Die Grundidee ist, find zu verwenden

  1. generieren Sie eine Liste von Dateien, die den allgemeinen Kriterien entsprechen.
  2. wenden Sie einen benutzerdefinierten Filter auf diese Liste an, und
  3. führen Sie eine Aktion aus, z. G. cp für die Einträge der gefilterten Liste.

Implementierung 1

(Erfordert, dass Bash durch Null-Bytes getrennte Datensätze liest.)

find /some/path -type f -print0 |
while read -rd '' f; do
  case "${f##*/}" in
    file1|file2|file3)
      printf '%s\0' "$f";;
  esac
done |
xargs -r0 -- cp -vt /some/other/path --

Jedes Rohr entspricht dem Beginn des nächsten Schritts der drei oben beschriebenen Schritte.

Die case -Anweisung hat den Vorteil, dass globbing Übereinstimmungen zulässig sind. Alternativ können Sie Bashs bedingte Ausdrücke verwenden:

if [[ "${f##*/}" == file1 || "${f##*/}" == file2 || "${f##*/}" == file3 ]]; then
  printf '%s\0' "$f"
fi

Implementierung 2

Wenn die Liste der zuzuordnenden Dateinamen etwas länger ist und nicht durch eine kleine Menge von globbing - Mustern ersetzt werden kann, oder wenn die Liste der zuzuordnenden Dateinamen zum Zeitpunkt des Schreibens nicht bekannt ist, haben Sie kann auf ein Array zurückgreifen, das die Liste der gewünschten Dateinamen enthält:

FILE_NAMES=( "file1" "file2" "file3" ... )

find /some/path -type f -print0 |
while read -rd '' f; do
  for needle in "${FILE_NAMES[@]}"; do
    if [ "${f##*/}" = "$needle" ]; then
      printf '%s\0' "$f"
    fi
  done
done |
xargs -r0 -- cp -vt /some/other/path --

Implementierung 3

Als Variation können wir ein assoziatives Array verwenden, das hoffentlich schnellere Suchzeiten als einfache "Listen" -Arrays hat:

declare -A FILE_NAMES=( ["file1"]= ["file2"]= ["file3"]= ... )  # Note the superscripts with []=

find /some/path -type f -print0 |
while read -rd '' f; do
  if [ -v FILE_NAMES["${f##*/}"] ]; then
    printf '%s\0' "$f"
  fi
done |
xargs -r0 -- cp -vt /some/other/path --
4
David Foerster

Zwei Probleme:

  1. for f in some_file iteriert nicht über den Inhalt von some_file, sondern nur über den Literal-String some_file. Verwenden Sie ein ordnungsgemäß implementiertes for -Konstrukt, um die while -Schleife zum Durchlaufen des Dateiinhalts zu umgehen.

  2. Variablen werden nicht erweitert, wenn sie in einfache Anführungszeichen gesetzt werden, in diesem Fall '$f'. Verwenden Sie doppelte Anführungszeichen, um Ihre ursprüngliche Idee umzusetzen.

Fügen Sie diese zusammen, sofern die Dateinamen in der file_list -Datei durch Zeilenumbrüche getrennt sind:

while IFS= read -r f; do 
    find /path1 -name "$f" -exec cp '{}' /path2 \;
done <file_list

Wenn Sie wissen, dass sich die Dateien im Verzeichnis /path1 und nicht in einem Unterverzeichnis davon befinden, können Sie einfach ein Array verwenden, um die Dateinamen abzurufen, und (GNU) cp direkt verwenden, wobei Sie wiederum eine neue Zeile annehmen getrennte Dateinamen in file_list:

( 
 IFS=$'\n'
 files=( $(<file_list) )
 cp -t /path2 "${files[@]}"
)

Bei einer großen Anzahl von Dateien ist es besser, die Datei einzeln zu wiederholen und cp, als sie in einem Array abzulegen:

while IFS= read -r f; do cp -- "$f" /path2; done <file_list

Wenn Sie eine Dateiliste wie z. file1 file2 file3 ... direkt im for -Konstrukt, dann genügt doppelte Anführungszeichen:

for f in file1 file2 file3; do 
    find /path1 -name "$f" -exec cp '{}' /path2 \;
done

Jetzt können Sie cp auch hier direkt verwenden, wenn sich nicht alle Dateien in einem Unterverzeichnis befinden würden:

cp -t /path2 file1 file2 file3

Oder Sie können statische absolute oder relative Pfade angeben, falls Sie cp nur verwenden möchten, um Dateien in einem beliebigen Unterverzeichnis zu bearbeiten.

7
heemayl

Sie haben klargestellt, dass Ihre Dateiliste keine Textdatei ist, sondern eine Reihe von Dateinamen, die Sie mit find suchen möchten. Sie haben erwähnt, dass dies für Sie funktioniert:

for f in file1 file2 file3 ... ; do find <path> -name "$f" \; done;

vermutlich heißt das, dass Sie die erwartete Liste der Dateien von diesem Befehl erhalten.

Sie sagen dann, dass dies nicht funktioniert:

for f in file1 file2 file3 ... ; do find <path> -name "$f" -exec cp '{}' <path2> \; done

vermutlich meinen Sie, dass die zuvor aufgelisteten Dateien nicht nach <path2> kopiert wurden. Ich bin mir nicht sicher, welchen Fehler Sie bekommen, aber wie es geschrieben steht, würde ich erwarten, dass dieser Befehl wie folgt hängt:

$for f in file1 file2 file3 ... ; do find <path> -name "$f" -exec cp '{}' <path2> \; done
>

warten auf den fehlenden ;. Vorausgesetzt, Sie haben dieses Problem behoben, fällt mir kein anderer offensichtlicher Grund ein, warum Ihr Befehl definitiv fehlschlagen würde. Sie sollten mit auskommen können

for f in file1 file2 file3 ... ; do find <path> -name "$f" -exec cp '{}' <path2> \; ; done

Ich schlage jedoch vor, das -t -Flag zu verwenden, um das Verzeichnis anzugeben. Ich muss mich bei David Foerster für diesen Kommentar bedanken, der meiner Meinung nach die beste Form für einen cp oder mv Aufruf mit find zeigt. . Es wird auch abgelehnt, doppelte Dateien zu überschreiben.

for f in file1 file2 file3; do find /path/to/search -name "$f" -exec cp -vt /path/to/destination -- {} + ; done

Anmerkungen

  • -v sei wortreich - sag uns, was getan wird
  • -t Verwendet das angegebene Verzeichnis als Ziel
  • -- akzeptiert keine weiteren Optionen
  • + Wenn es mehrere Übereinstimmungen gibt (in Ihrem Fall ist dies unwahrscheinlich, da for für jedes Element in der Dateiliste einmal ausgeführt wird, aber möglicherweise mehrere übereinstimmende Dateien für jeden Namen vorhanden sind), erstellen Sie ein Argument Liste für cp anstatt cp mehrmals auszuführen
  • ; trennt Befehle in der Shell. Die Form einer for -Schleife ist

    for var in things; do command "$var"; done
    

    Wenn der ; nicht vor done angezeigt wird, wartet die Bash auf ; oder ein Interrupt-Signal.

5
Zanna