Wie kann ich zufällige Dateien aus einem Verzeichnis in Bash auswählen?
143
Ich habe ein Verzeichnis mit ca. 2000 Dateien. Wie kann ich eine zufällige Auswahl von NDateien mithilfe eines Bash-Skripts oder einer Liste von Piped-Befehlen auswählen ?
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.
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:
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!
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.
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.
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.
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
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.
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:
#!/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 arraydone
ls | shuf -n 5
Quelle von Unix StackexchangeAntworten:
Hier ist ein Skript, das die zufällige Option der GNU-Sortierung verwendet:
quelle
"$file"
, nicht gezeigt, wäre raumempfindlich.ls
?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: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:quelle
N
zufällige Dateien auswählen , daher ist die Verwendung1
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 Arrayrandf
mit einer Liste zufälliger Dateien. Dieses Array kann beiprintf '%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.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, dassN
dies nicht direkt von Benutzereingaben kommt, ohne gründlich überprüft zu werden!Ich persönlich mag nicht
eval
und daher diese Antwort!Das gleiche mit einer einfacheren Methode (einer Schleife):
Wenn Sie möglicherweise nicht mehrmals dieselbe Datei haben möchten:
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.quelle
"{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.quelle
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ürdefind . -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:Ersetzen Sie
echo
durch den Befehl, den Sie für Ihre Dateien ausführen möchten.quelle
read
nicht die gleichen Probleme wie das Parsenls
? Es liest nämlich Zeile für Zeile, sodass es nicht für Dateien mit Zeilenumbrüchen im Namen funktioniertWenn 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)
N
Verwenden Sie zum Auswählen von Dateien / Zeilen (HinweisN
am Ende des Befehls, ersetzen Sie diesen durch eine Zahl)quelle
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:
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:
Dadurch wird eine Shell-Option aktiviert , die eine
**
rekursive Übereinstimmung bewirkt . Jetzt$a
enthält Ihr Array jede Datei in der gesamten Hierarchie.quelle
Wenn Sie mehr Dateien in Ihrem Ordner haben, können Sie den folgenden Pipeline-Befehl verwenden, den ich in Unix StackExchange gefunden habe .
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
.quelle
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/
quelle
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.
quelle
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.
quelle
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?
quelle