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

Antworten:

180

Hier ist ein Skript, das die zufällige Option der GNU-Sortierung 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
Josh Lee
quelle
Cool, wusste nicht, Sorte -R; Früher habe ich Bogosort zuvor :-p
alex
5
sort: ungültige Option - R Versuchen Sie "sort --help" für weitere Informationen.
2
Scheint nicht für Dateien zu funktionieren, die Leerzeichen enthalten.
Houshalter
Dies sollte für Dateien mit Leerzeichen funktionieren (die Pipeline verarbeitet Zeilen). Es funktioniert nicht für Namen mit Zeilenumbruch. Nur die Verwendung von "$file", nicht gezeigt, wäre raumempfindlich.
Yann Vernier
106

Sie können dafür shuf(aus dem GNU coreutils-Paket) verwenden. Geben Sie ihm einfach eine Liste mit Dateinamen und bitten Sie ihn, 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..

Passen Sie den -n, --head-count=COUNTWert an, 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
Nordischer Mainframe
quelle
4
OP wollte Nzufällige Dateien auswählen , daher ist die Verwendung 1etwas irreführend.
Aioobe
4
Wenn Sie Dateinamen mit Zeilenumbrüchen haben:find dirname -type f -print0 | shuf -zn1
Hitechcomputergeek
4
Was ist, wenn ich diese zufällig ausgewählten Dateien in einen anderen Ordner kopieren muss? Wie werden Operationen an diesen zufällig ausgewählten Dateien ausgeführt?
Rishabh Agrahari
18

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

  • Dieser gibt möglicherweise dieselbe Datei mehrmals aus und Nmuss 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, Ihnen aber die vorherige Möglichkeit wirklich gefallen hat, können Sie sie verwenden eval. Aber es ist böse, und Sie müssen wirklich sicherstellen, dass Ndies 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 nicht evalund daher diese Antwort!

  • Das gleiche 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 späte Antwort auf einen alten Beitrag, aber die akzeptierte Antwort verweist auf eine externe Seite, die schrecklich istüben, und die andere Antwort ist nicht viel besser, da es auch die Ausgabe von analysiert ls. Ein Kommentar zur akzeptierten Antwort weist auf eine ausgezeichnete Antwort von Lhunath hin, die offensichtlich gute Praxis zeigt, aber das OP nicht genau beantwortet.

gniourf_gniourf
quelle
Erste und zweite erzeugten "schlechte Substitution"; es gefiel nicht, dass der "{1..42}"Teil eine Spur hinterließ "1". Außerdem $RANDOMist es nur 15 Bit und die Methode funktioniert nicht mit über 32767 Dateien zur Auswahl.
Yann Vernier
12
ls | shuf -n 10 # ten random files
Silgon
quelle
1
Sie sollten sich nicht auf die Ausgabe von verlassen ls. Dies funktioniert nicht, wenn z. B. ein Dateiname Zeilenumbrüche enthält.
Bfontaine
3
@bfontaine Sie scheinen von Zeilenumbrüchen in Dateinamen heimgesucht zu werden :). Sind sie wirklich so häufig? Mit anderen Worten, gibt es ein Tool, das Dateien mit Zeilenumbrüchen im Namen erstellt? Da es als Benutzer sehr schwierig ist, einen solchen Dateinamen zu erstellen. Gleiches gilt für Dateien aus dem Internet
Ciprian Tomoiagă
3
@CiprianTomoiaga Das ist ein Beispiel für die Probleme, die auftreten können. lsEs wird nicht garantiert, dass Sie "saubere" Dateinamen erhalten, daher sollten Sie sich nicht darauf verlassen, Punkt. Die Tatsache, dass diese Probleme selten oder ungewöhnlich sind, ändert nichts an dem Problem. vor allem, wenn es dafür bessere Lösungen gibt.
Bfontaine
lskann Verzeichnisse und Leerzeilen enthalten. Ich würde find . -type f | shuf -n10stattdessen so etwas vorschlagen .
cherdt
9

Eine einfache Lösung, um 5zufällige Dateien auszuwählen und gleichzeitig das Parsen von ls zu vermeiden . Es funktioniert auch mit Dateien, die Leerzeichen, Zeilenumbrüche und andere Sonderzeichen enthalten:

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

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

scai
quelle
1
Nun, hat die Pipe + readnicht die gleichen Probleme wie das Parsen ls? Es liest nämlich Zeile für Zeile, sodass es nicht für Dateien mit Zeilenumbrüchen im Namen funktioniert
Ciprian Tomoiagă
3
Du hast recht. Meine vorherige Lösung funktionierte nicht für Dateinamen, die Zeilenumbrüche enthalten, und funktioniert wahrscheinlich auch bei anderen mit bestimmten Sonderzeichen. Ich habe meine Antwort aktualisiert, um die Nullterminierung anstelle von Zeilenumbrüchen zu verwenden.
Scai
4

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

Verwenden Sie zum Auswählen einer Datei (oder Zeile aus einem beliebigen Befehl)

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

NVerwenden Sie zum Auswählen von Dateien / Zeilen (Hinweis Nam Ende des Befehls, ersetzen Sie diesen durch eine Zahl)

ls -1 | python -c "import sys; import random; print(''.join(random.sample(sys.stdin.readlines(), int(sys.argv[1]))).rstrip())" N
Kennzeichen
quelle
Dies funktioniert nicht, wenn Ihr Dateiname Zeilenumbrüche enthält.
Bfontaine
4

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

Ich habe jedoch einige Minuten gebraucht, um die "nicht sehr gut dokumentierten" Funktionen zu entwirren, die in dieser Antwort verwendet werden. Wenn Ihre Bash-Fähigkeiten solide genug sind, dass Sie sofort gesehen haben, wie es funktioniert, überspringen Sie diesen Kommentar. Aber ich habe es nicht getan, und nachdem ich es entwirrt habe, denke ich, dass es sich lohnt, es zu erklären.

Feature # 1 ist das Globbing der Shell-eigenen Datei. a=(*)Erstellt ein Array, $adessen Mitglieder die Dateien im aktuellen Verzeichnis sind. Bash versteht alle Verrücktheiten von Dateinamen, so dass die Liste garantiert korrekt, garantiert maskiert usw. ist. Sie müssen sich keine Gedanken über das ordnungsgemäße Parsen der von zurückgegebenen Textdateinamen machenls .

Feature 2 sind Bash- Parametererweiterungen für Arrays , die in einem anderen verschachtelt sind. Dies beginnt mit ${#ARRAY[@]}, das sich auf die Länge von erweitert $ARRAY.

Diese Erweiterung wird dann verwendet, um das Array zu zeichnen. Die Standardmethode zum Finden einer Zufallszahl zwischen 1 und N besteht darin, den Wert der Zufallszahl Modulo N zu verwenden. Wir möchten 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 führt dies jedoch in einer einzigen Zeile aus und entfernt die unnötige Variablenzuweisung.

Feature Nr. 3 ist die Erweiterung der Bash-Klammer , obwohl ich zugeben muss, dass ich sie nicht ganz verstehe. Brace Expansion verwendet wird , zum Beispiel eine Liste von 25 Dateien zu generieren genannt filename1.txt, filename2.txtusw: 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 Klammererweiterung setzt eine einzelne Ziffer zwischen ]und} , von der ich zuerst dachte, dass sie das Array abonniert, aber wenn ja, würde ein Doppelpunkt vorangestellt. (Es hätte auch 42 aufeinanderfolgende Elemente von einer zufälligen Stelle im Array zurückgegeben, was keineswegs mit der Rückgabe von 42 zufälligen Elementen aus dem Array identisch ist.) Ich denke, es bringt die Shell nur dazu, die Erweiterung 42 Mal auszuführen und damit 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 eine Shell-Option aktiviert , die eine **rekursive Übereinstimmung bewirkt . Jetzt $aenthält Ihr Array jede Datei in der gesamten Hierarchie.

Ken
quelle
2

Wenn Sie mehr Dateien in Ihrem Ordner haben, können Sie den folgenden Pipeline-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, den ich verwendet habe cp.

Bhaskar Chakradhar
quelle
1

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

ls Befehl: Wie kann ich eine rekursive vollständige Pfadliste 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
Benmarbles
quelle
1

MacOS verfügt nicht über die Befehle sort -R und shuf , daher benötigte ich eine reine Bash-Lösung, die alle Dateien ohne Duplikate randomisiert und 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 mit einem Zähler mit if oder gniourf_gniourfs for-Schleife mit N anzuhalten. $ RANDOM ist auf ~ 32000 Dateien beschränkt, dies sollte jedoch in den meisten Fällen der Fall sein.

#!/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
Katze
quelle
0

Ich benutze dies: Es verwendet eine temporäre Datei, geht aber tief in ein Verzeichnis, bis es eine reguläre Datei findet und sie 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;
bzimage
quelle