2009-01-05 130 views
76

我有一個包含大約2000個文件的目錄。如何通過使用bash腳本或管道命令列表來選擇N文件的隨機樣本?如何從bash中的目錄中選擇隨機文件?

+1

在Unix和Linux上也是一個很好的答案:http://unix.stackexchange.com/a/38344/24170 – 2015-11-23 03:40:35

+7

`ls | SHUF -n 5` [來源從Unix Stackexchange](http://unix.stackexchange.com/a/48477/14993) – jgomo3 2017-01-26 18:37:22

+0

類似:https://stackoverflow.com/questions/2153882/how-can-i-shuffle -th-line-of-the-unix-command-line-or-in-a-shel – AAAfarmclub 2017-06-06 02:05:58

回答

107

下面是一個使用GNU的排序是隨機選擇的腳本:

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 
+0

酷,不知道排序-R;我之前使用過bogosort :-p – alex 2009-01-05 21:23:28

+4

排序:無效選項 - R 嘗試`sort --help'以獲取更多信息。 – 2015-10-28 06:28:42

+1

似乎不適用於文件中有空格的文件。 – Houshalter 2017-03-17 22:46:13

15

這裏是不解析的ls輸出幾種可能性,並且是100%安全的關於在空間和有趣的符號文件,其名稱。所有這些將隨機文件列表填充數組randf。如果需要,可以使用printf '%s\n' "${randf[@]}"輕鬆打印此陣列。

  • 這一次很可能會輸出相同的文件幾次,N需要預先知道。這裏我選擇了N = 42。

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

    此功能沒有很好的記錄。

  • 如果事先不知道N,但是您確實喜歡以前的可能性,則可以使用eval。但它是邪惡的,你必須確保N不是直接來自用戶輸入,沒有徹底檢查!

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

    我個人不喜歡eval,因此這個答案!

  • 使用更簡單的方法相同的(循環):

    N=42 
    a=(*) 
    randf=() 
    for((i=0;i<N;++i)); do 
        randf+=("${a[RANDOM%${#a[@]}]}") 
    done 
    
  • 如果你不想有可能多次相同的文件:

    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 
    

注意。對於舊帖子,這是一個遲到的答案,但接受的答案鏈接到顯示可怕的練習的外部頁面,而另一個答案並不好,因爲它也分析了ls的輸出。對接受答案的評論指出,Lhunath的一個很好的答案顯然表明了良好的做法,但並沒有完全回答OP。

62

你可以使用shuf(來自GNU coreutils包)。只要給它的文件名列表,並要求它從一個隨機排列返回的第一行:

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

調整​​值返回想要的行數。例如,要返回5個隨機文件名,您可以使用:

find dirname -type f | shuf -n 5 
1

這是我可以在MacOS上使用bash進行遊戲的唯一腳本。我結合,並從以下兩個鏈接編輯片段:

ls command: how can I get a recursive full-path listing, one line per file?

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 
0

我用這樣的:它使用的臨時文件,但目錄中的深進,直到它找到一個普通文件和回報它。

# 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; 
3

如果您已經安裝了Python(可與任意的Python 2或Python 3):

要選擇一個文件(或線從任意命令),使用

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

選擇N文件/行,使用(注意N位於命令的末尾,用數字代替)

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

這是對@ gniourf_gniourf遲到的答案的一個更晚的迴應,我剛剛提出,因爲這是迄今爲止最好的答案,兩次。 (一次爲了避免eval,一次爲了安全的文件名處理。)

但是花了我幾分鐘才解開了這個答案使用的「沒有很好記錄」的功能。如果你的Bash技能足夠穩固,你立即看到它的工作原理,那麼跳過這個評論。但我沒有,並解開它,我認爲這是值得解釋的。

功能#1是shell自己的文件通配符。 a=(*)創建一個數組,$a,其成員是當前目錄中的文件。 Bash理解文件名的所有奇怪之處,以便列表保證正確,保證轉義等。無需擔心由ls返回的正確解析文本文件名稱。

特徵#2是Bash parameter expansionsarrays,一個嵌套在另一個。這開始於${#ARRAY[@]},其擴展到$ARRAY的長度。

該擴展然後用於下標數組。尋找1到N之間隨機數的標準方法是取模隨機數N的值。我們需要一個介於0和數組長度之間的隨機數。這裏的方法,分爲兩行清晰的緣故

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

但這種方法做它在一個單一的線,去除不必要的變量賦值。

功能#3Bash brace expansion,雖然我不得不承認我不完全理解它。例如,使用Brace擴展來生成名爲filename1.txtfilename2.txt等的25個文件的列表:echo "filename"{1..25}".txt"

上面的子shellhell中的表達式"${a[RANDOM%${#a[@]}]"{1..42}"}"使用該技巧來產生42個單獨的擴展。括號擴展在]}之間放置了一個數字,起初我以爲是對數組進行下標,但如果是這樣,則會在冒號前加上數字。(它也會從數組中的一個隨機點中返回42個連續項,這與從數組中返回42個隨機項完全不同。)我認爲這只是讓shell運行擴展42次,從而返回來自陣列的42個隨機項目。 (但是,如果有人能更充分地解釋,我很想聽聽吧。)

的原因N有被硬編碼(42)爲變量擴展前的括號擴展情況。

最後,這裏的特點#4,如果你想爲一個目錄層次結構遞歸地做到這一點:

shopt -s globstar 
a=(**) 

這將打開一個shell option導致**遞歸匹配。現在您的$a數組包含整個層次結構中的每個文件。

1

的簡單解決方案,用於選擇5隨機文件,同時avoiding to parse ls。它還可以與含有空格,換行和其他特殊字符的文件:

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

替換echo您要執行的文件的命令。

2
ls | shuf -n 10 # ten random files 
1

的MacOS沒有排序-RSHUF命令,所以我需要一個bash只隨機化的所有文件沒有重複,並沒有發現,這裏的解決方案。該解決方案與gniourf_gniourf的解決方案#4類似,但希望能增加更好的評論。

腳本應該很容易修改,以便在N個樣本使用帶if的計數器後停止,或者gniourf_gniourf的for循環使用N. $ RANDOM限制爲~32000個文件,但對大多數情況應該這樣做。

#!/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