it-swarm.com.de

Wie kann ich zufällige Dateien aus einem Verzeichnis in Bash auswählen?

Ich habe ein Verzeichnis mit ca. 2000 Dateien. Wie kann ich eine zufällige Stichprobe von N Dateien mithilfe eines Bash-Skripts oder einer Liste weitergeleiteter Befehle auswählen?

119
Marlo Guthrie

Hier ist ein Skript, das die Zufallsoption GNU sort verwendet:

ls |sort -R |tail -$N |while read file; do
    # Something involving $file, or you can leave
    # off the while to just get the filenames
done
156
Josh Lee

Sie können dafür shuf (aus dem GNU coreutils-Paket) verwenden. Geben Sie einfach eine Liste der Dateinamen ein und bitten Sie es, die erste Zeile einer zufälligen Permutation zurückzugeben:

ls dirname | shuf -n 1
# probably faster and more flexible:
find dirname -type f | shuf -n 1
# etc..

Verstelle die -n, --head-count=COUNT value, um die Anzahl der gewünschten Zeilen zurückzugeben. Um beispielsweise 5 zufällige Dateinamen zurückzugeben, würden Sie Folgendes verwenden:

find dirname -type f | shuf -n 5
84

Hier sind einige Möglichkeiten, die die Ausgabe von ls nicht analysieren und die in Bezug auf Dateien mit Leerzeichen und lustigen Symbolen im Namen 100% sicher sind. Alle von ihnen füllen ein Array randf mit einer Liste zufälliger Dateien. Dieses Array kann bei Bedarf einfach mit printf '%s\n' "${randf[@]}" Ausgedruckt werden.

  • Diese gibt möglicherweise dieselbe Datei mehrmals aus und N muss im Voraus bekannt sein. Hier habe ich N = 42 gewählt.

    a=( * )
    randf=( "${a[RANDOM%${#a[@]}]"{1..42}"}" )
    

    Diese Funktion ist nicht sehr gut dokumentiert.

  • Wenn N nicht im Voraus bekannt ist, aber Ihnen die vorherige Möglichkeit wirklich gefallen hat, können Sie eval verwenden. Aber es ist böse und Sie müssen wirklich sicherstellen, dass N nicht direkt von Benutzereingaben kommt, ohne gründlich überprüft zu werden!

    N=42
    a=( * )
    eval randf=( \"\${a[RANDOM%\${#a[@]}]\"\{1..$N\}\"}\" )
    

    Ich persönlich mag eval nicht und daher diese Antwort!

  • Dasselbe mit einer einfacheren Methode (einer Schleife):

    N=42
    a=( * )
    randf=()
    for((i=0;i<N;++i)); do
        randf+=( "${a[RANDOM%${#a[@]}]}" )
    done
    
  • Wenn Sie möglicherweise nicht mehrmals dieselbe Datei haben möchten:

    N=42
    a=( * )
    randf=()
    for((i=0;i<N && ${#a[@]};++i)); do
        ((j=RANDOM%${#a[@]}))
        randf+=( "${a[j]}" )
        a=( "${a[@]:0:j}" "${a[@]:j+1}" )
    done
    

Hinweis . Dies ist eine verspätete Antwort auf einen alten Beitrag, aber die akzeptierte Antwort verweist auf eine externe Seite, die schreckliches bash zeigt, und die andere Antwort ist nicht viel besser, da sie auch die Ausgabe von ls. Ein Kommentar zu der akzeptierten Antwort weist auf eine ausgezeichnete Antwort von Lhunath hin, die offensichtlich gute Praxis zeigt, aber das OP nicht genau beantwortet.

18
gniourf_gniourf
ls | shuf -n 10 # ten random files
9
silgon

Eine einfache Lösung zur Auswahl von 5 zufällige Dateien während Vermeiden, ls zu analysieren . Es funktioniert auch mit Dateien, die Leerzeichen, Zeilenumbrüche und andere Sonderzeichen enthalten:

shuf -ezn 5 * | xargs -0 -n1 echo

Ersetzen Sie echo durch den Befehl, den Sie für Ihre Dateien ausführen möchten.

7
scai

Wenn Sie Python installiert haben (funktioniert entweder mit Python 2 oder Python 3)):

Verwenden Sie, um eine Datei (oder Zeile aus einem beliebigen Befehl) auszuwählen

ls -1 | python -c "import sys; import random; print(random.choice(sys.stdin.readlines()).rstrip())"

Um N Dateien/Zeilen auszuwählen, verwenden Sie (Anmerkung N steht am Ende des Befehls, ersetzen Sie dies durch eine Zahl).

ls -1 | python -c "import sys; import random; print(''.join(random.sample(sys.stdin.readlines(), int(sys.argv[1]))).rstrip())" N
4
Mark

Dies ist eine noch spätere Antwort auf die verspätete Antwort von @ gniourf_gniourf, die ich gerade positiv bewertet habe, weil es bei weitem die beste Antwort ist, zweimal. (Einmal zur Vermeidung von eval und einmal zur sicheren Behandlung von Dateinamen.)

Ich habe jedoch einige Minuten gebraucht, um die in dieser Antwort verwendeten "nicht sehr gut dokumentierten" Funktionen zu entwirren. Wenn deine Bash-Fähigkeiten solide genug sind, dass du sofort gesehen hast, wie es funktioniert, dann überspringe diesen Kommentar. Aber ich habe es nicht getan, und nachdem ich es entwirrt habe, denke ich, dass es eine Erklärung wert ist.

Feature # 1 ist das Globbing der Shell-eigenen Datei. a=(*) erstellt ein Array, $a, dessen Mitglieder die Dateien im aktuellen Verzeichnis sind. Bash versteht alle Verrücktheiten von Dateinamen, so dass die Liste garantiert korrekt ist, garantiert maskiert wird usw. Sie müssen sich keine Gedanken darüber machen, wie die von ls zurückgegebenen Textdateinamen richtig analysiert werden.

Feature # 2 ist Bash Parametererweiterungen für Arrays , die ineinander verschachtelt sind. Dies beginnt mit ${#ARRAY[@]}, Das sich auf die Länge von $ARRAY Erweitert.

Diese Erweiterung wird dann verwendet, um das Array zu subskribieren. Der Standardweg, um eine Zufallszahl zwischen 1 und N zu finden, besteht darin, den Wert der Zufallszahl modulo N zu nehmen. Wir wollen eine Zufallszahl zwischen 0 und der Länge unseres Arrays. Hier ist der Ansatz, der der Klarheit halber in zwei Zeilen unterteilt ist:

LENGTH=${#ARRAY[@]}
RANDOM=${a[RANDOM%$LENGTH]}

Diese Lösung erledigt dies jedoch in einer einzigen Zeile und beseitigt die unnötige Variablenzuweisung.

Feature # 3 ist Bash Brace Expansion , obwohl ich zugeben muss, dass ich es nicht ganz verstehe. Die geschweifte Klammer wird beispielsweise verwendet, um eine Liste von 25 Dateien mit den Namen filename1.txt, filename2.txt Usw. zu erstellen: echo "filename"{1..25}".txt".

Der Ausdruck in der obigen Unterschale, "${a[RANDOM%${#a[@]}]"{1..42}"}", Verwendet diesen Trick, um 42 separate Erweiterungen zu erzeugen. Die geschweifte Klammer setzt eine einzelne Ziffer zwischen den Buchstaben ] Und }, Von denen ich anfangs dachte, dass sie das Array subskribieren, aber in diesem Fall würde ein Doppelpunkt davor stehen. (Es hätte auch 42 aufeinanderfolgende Elemente von einer zufälligen Stelle im Array zurückgegeben, was keineswegs dasselbe ist wie das Zurückgeben von 42 zufälligen Elementen aus dem Array.) Ich denke, es bringt die Shell nur dazu, die Erweiterung 42 Mal auszuführen und dadurch zurückzukehren 42 zufällige Elemente aus dem Array. (Aber wenn jemand es genauer erklären kann, würde ich es gerne hören.)

Der Grund, warum N fest codiert werden muss (bis 42), ist, dass die Klammererweiterung vor der variablen Erweiterung erfolgt.

Schließlich ist hier Feature 4 , wenn Sie dies rekursiv für eine Verzeichnishierarchie tun möchten:

shopt -s globstar
a=( ** )

Dadurch wird ein Shell-Option aktiviert, das dazu führt, dass ** Rekursiv übereinstimmt. Jetzt enthält Ihr Array $a Jede Datei in der gesamten Hierarchie.

4
Ken

MacOS verfügt nicht über die Befehle sort -R und shuf , also habe ich brauchte eine bash only Lösung, die alle Dateien zufällig sortiert ohne Duplikate und das hier nicht gefunden hat. Diese Lösung ähnelt der Lösung Nr. 4 von gniourf_gniourf, fügt jedoch hoffentlich bessere Kommentare hinzu.

Das Skript sollte leicht zu ändern sein, um nach N Samples anzuhalten, indem ein Zähler mit if verwendet wird, oder gniourf_gniourf's for-Schleife mit N. $ RANDOM ist auf ~ 32000 Dateien beschränkt, aber das sollte in den meisten Fällen reichen.

#!/bin/bash

array=(*)  # this is the array of files to shuffle
# echo ${array[@]}
for dummy in "${array[@]}"; do  # do loop length(array) times; once for each file
    length=${#array[@]}
    randomi=$(( $RANDOM % $length ))  # select a random index

    filename=${array[$randomi]}
    echo "Processing: '$filename'"  # do something with the file

    unset -v "array[$randomi]"  # set the element at index $randomi to NULL
    array=("${array[@]}")  # remove NULL elements introduced by unset; copy array
done
1
cat

Dies ist das einzige Skript, mit dem ich Nice mit bash unter MacOS spielen kann. Ich habe Ausschnitte aus den folgenden beiden Links kombiniert und bearbeitet:

ls Befehl: Wie kann ich eine rekursive vollständige Pfadauflistung erhalten, eine Zeile pro Datei?

http://www.linuxquestions.org/questions/linux-general-1/is-there-a-bash-command-for-picking-a-random-file-678687/

#!/bin/bash

# Reads a given directory and picks a random file.

# The directory you want to use. You could use "$1" instead if you
# wanted to parametrize it.
DIR="/path/to/"
# DIR="$1"

# Internal Field Separator set to newline, so file names with
# spaces do not break our script.
IFS='
'

if [[ -d "${DIR}" ]]
then
  # Runs ls on the given dir, and dumps the output into a matrix,
  # it uses the new lines character as a field delimiter, as explained above.
  #  file_matrix=($(ls -LR "${DIR}"))

  file_matrix=($(ls -R $DIR | awk '; /:$/&&f{s=$0;f=0}; /:$/&&!f{sub(/:$/,"");s=$0;f=1;next}; NF&&f{ print s"/"$0 }'))
  num_files=${#file_matrix[*]}

  # This is the command you want to run on a random file.
  # Change "ls -l" by anything you want, it's just an example.
  ls -l "${file_matrix[$((RANDOM%num_files))]}"
fi

exit 0
1
benmarbles

Ich benutze dies: Es verwendet eine temporäre Datei, geht aber tief in ein Verzeichnis, bis es eine reguläre Datei findet und zurückgibt.

# find for a quasi-random file in a directory tree:

# directory to start search from:
ROOT="/";  

tmp=/tmp/mytempfile    
TARGET="$ROOT"
FILE=""; 
n=
r=
while [ -e "$TARGET" ]; do 
    TARGET="$(readlink -f "${TARGET}/$FILE")" ; 
    if [ -d "$TARGET" ]; then
      ls -1 "$TARGET" 2> /dev/null > $tmp || break;
      n=$(cat $tmp | wc -l); 
      if [ $n != 0 ]; then
        FILE=$(shuf -n 1 $tmp)
# or if you dont have/want to use shuf:
#       r=$(($RANDOM % $n)) ; 
#       FILE=$(tail -n +$(( $r + 1 ))  $tmp | head -n 1); 
      fi ; 
    else
      if [ -f "$TARGET"  ] ; then
        rm -f $tmp
        echo $TARGET
        break;
      else 
        # is not a regular file, restart:
        TARGET="$ROOT"
        FILE=""
      fi
    fi
done;
0
bzimage

Wenn Sie mehr Dateien in Ihrem Ordner haben, können Sie den folgenden Pipe-Befehl verwenden, den ich in unix stackexchange gefunden habe.

find /some/dir/ -type f -print0 | xargs -0 shuf -e -n 8 -z | xargs -0 cp -vt /target/dir/

Hier wollte ich die Dateien kopieren, aber wenn Sie Dateien verschieben oder etwas anderes tun möchten, ändern Sie einfach den letzten Befehl, in dem ich cp verwendet habe.

0