Ich habe ein Verzeichnis mit ca. 2000 Dateien. Wie kann ich eine zufällige Auswahl von N
Dateien mithilfe eines Bash-Skripts oder einer Liste von Piped-Befehlen auswählen ?
ls | shuf -n 5
Quelle von Unix Stackexchange
Ich habe ein Verzeichnis mit ca. 2000 Dateien. Wie kann ich eine zufällige Auswahl von N
Dateien mithilfe eines Bash-Skripts oder einer Liste von Piped-Befehlen auswählen ?
ls | shuf -n 5
Quelle von Unix Stackexchange
Antworten:
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
"$file"
, nicht gezeigt, wäre raumempfindlich.
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=COUNT
Wert 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
N
zufällige Dateien auswählen , daher ist die Verwendung 1
etwas irreführend.
find dirname -type f -print0 | shuf -zn1
Hier sind einige Möglichkeiten, die die Ausgabe von nicht analysieren ls
und die in Bezug auf Dateien mit Leerzeichen und lustigen Symbolen im Namen 100% sicher sind. Alle füllen ein Array randf
mit 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 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, 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 N
dies 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 eval
und 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 istBashü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.
"{1..42}"
Teil eine Spur hinterließ "1"
. Außerdem $RANDOM
ist es nur 15 Bit und die Methode funktioniert nicht mit über 32767 Dateien zur Auswahl.
ls | shuf -n 10 # ten random files
ls
. Dies funktioniert nicht, wenn z. B. ein Dateiname Zeilenumbrüche enthält.
ls
Es 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.
ls
kann Verzeichnisse und Leerzeilen enthalten. Ich würde find . -type f | shuf -n10
stattdessen so etwas vorschlagen .
Eine einfache Lösung, um 5
zufä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 echo
durch den Befehl, den Sie für Ihre Dateien ausführen möchten.
read
nicht 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
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())"
N
Verwenden Sie zum Auswählen von Dateien / Zeilen (Hinweis N
am 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
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 eval
und 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, $a
dessen 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.txt
usw: 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 $a
enthält Ihr Array jede Datei in der gesamten Hierarchie.
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
.
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?
#!/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
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
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;
Wie wäre es mit einer Perl-Lösung, die hier von Mr. Kang leicht behandelt wurde:
Wie kann ich die Zeilen einer Textdatei in der Unix-Befehlszeile oder in einem Shell-Skript mischen?
$ ls | perl -MList :: Util = shuffle -e '@lines = shuffle (<>); print @lines [0..4] '