ฉันจะเลือกไฟล์สุ่มจากไดเรกทอรีในทุบตีได้อย่างไร


144

ฉันมีไดเรกทอรีที่มีไฟล์ประมาณ 2,000 ไฟล์ ฉันจะเลือกตัวอย่างNไฟล์แบบสุ่มโดยใช้ bash script หรือรายการคำสั่ง piped ได้อย่างไร


1
ยังเป็นคำตอบที่ดีที่ Unix & Linux: unix.stackexchange.com/a/38344/24170
Nikana Reklawyks


ที่คล้ายกัน: stackoverflow.com/questions/2153882/…
AAAfarmclub

คำตอบ:


180

นี่คือสคริปต์ที่ใช้ตัวเลือกการเรียงลำดับแบบสุ่มของ 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

เจ๋งไม่รู้เรื่อง -R; ก่อนหน้านี้ฉันใช้ bogosort :-p
alex

5
sort: ตัวเลือกที่ไม่ถูกต้อง - R ลอง `sort - ช่วย 'สำหรับข้อมูลเพิ่มเติม

2
ดูเหมือนจะใช้งานไม่ได้กับไฟล์ที่มีช่องว่างอยู่
Houshalter

สิ่งนี้ควรใช้ได้กับไฟล์ที่มีช่องว่าง (ไปป์ไลน์ประมวลผล) มันใช้ไม่ได้กับชื่อที่มีบรรทัดใหม่ในนั้น เฉพาะการใช้งานที่"$file"ไม่แสดงจะมีความอ่อนไหวต่อช่องว่าง
Yann Vernier


108

คุณสามารถใช้shuf(จากแพ็คเกจ GNU coreutils) ได้ เพียงป้อนรายการชื่อไฟล์และขอให้ส่งคืนบรรทัดแรกจากการเปลี่ยนรูปแบบสุ่ม:

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

ปรับ-n, --head-count=COUNTค่าเพื่อส่งคืนจำนวนบรรทัดที่ต้องการ ตัวอย่างเช่นเพื่อส่งคืน 5 ชื่อไฟล์สุ่มที่คุณจะใช้:

find dirname -type f | shuf -n 5

4
OP ต้องการเลือกNไฟล์สุ่มดังนั้นการใช้จึง1เป็นเรื่องเข้าใจผิดเล็กน้อย
aioobe

4
หากคุณมีชื่อไฟล์ที่มีการขึ้นบรรทัดใหม่:find dirname -type f -print0 | shuf -zn1
Hitechcomputergeek

5
จะเป็นอย่างไรถ้าฉันต้องคัดลอกไฟล์ที่เลือกแบบสุ่มเหล่านี้ไปยังโฟลเดอร์อื่น วิธีการดำเนินการกับไฟล์ที่เลือกแบบสุ่มเหล่านี้?
Rishabh Agrahari

18

นี่คือความเป็นไปได้บางอย่างที่ไม่แยกวิเคราะห์ผลลัพธ์ของ lsและปลอดภัย 100% เกี่ยวกับไฟล์ที่มีช่องว่างและสัญลักษณ์ตลกในชื่อ พวกเขาทั้งหมดจะเติมอาเรย์randfด้วยรายการของไฟล์สุ่ม อาร์เรย์นี้จะถูกพิมพ์อย่างง่ายดายด้วยprintf '%s\n' "${randf[@]}"ถ้าจำเป็น

  • อันนี้อาจจะส่งออกไฟล์เดียวกันหลายครั้งและ Nจะต้องรู้ล่วงหน้า ที่นี่ฉันเลือก N = 42

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

    คุณสมบัตินี้ไม่ได้บันทึกไว้อย่างดี

  • ถ้าไม่มีไม่เป็นที่รู้จักล่วงหน้า evalแต่คุณชอบความเป็นไปได้ก่อนหน้านี้คุณสามารถใช้ แต่มันก็ชั่วร้ายและคุณต้องทำให้แน่ใจNว่าไม่ได้มาจากการป้อนข้อมูลของผู้ใช้โดยตรงโดยไม่ต้องตรวจสอบอย่างละเอียด!

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

    ฉันไม่ชอบเป็นการส่วนตัวevalและด้วยเหตุนี้คำตอบนี้!

  • เช่นเดียวกันโดยใช้วิธีการที่ตรงไปตรงมามากขึ้น (a loop):

    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 อย่างแน่นอน


ตัวแรกและตัวที่สองผลิต "การทดแทนที่ไม่ดี"; มันไม่ชอบ"{1..42}"ตอนที่ทิ้งร่องรอย"1"ไว้ นอกจากนี้$RANDOMเป็นเพียง 15 บิตและวิธีการนี้จะไม่ทำงานกับไฟล์มากกว่า 32767 ไฟล์ให้เลือก
Yann Vernier

13
ls | shuf -n 10 # ten random files

1
lsคุณไม่ควรพึ่งพาการส่งออกของ สิ่งนี้จะไม่ทำงานหากเช่นชื่อไฟล์มีการขึ้นบรรทัดใหม่
bfontaine

3
@bfontaine ดูเหมือนว่าคุณจะถูกผีสิงขึ้นบรรทัดใหม่ในชื่อไฟล์ :) พวกเขาเป็นเรื่องธรรมดาที่จริงเหรอ? กล่าวอีกนัยหนึ่งมีเครื่องมือบางอย่างที่สร้างไฟล์ที่มีบรรทัดใหม่ในชื่อของพวกเขาหรือไม่? เนื่องจากเป็นผู้ใช้จึงเป็นเรื่องยากมากที่จะสร้างชื่อไฟล์ดังกล่าว เช่นเดียวกันกับไฟล์ที่มาจากอินเทอร์เน็ต
Ciprian Tomoiagă

3
@CiprianTomoiaga นั่นเป็นตัวอย่างของปัญหาที่คุณอาจได้รับ lsไม่รับประกันว่าจะให้ชื่อไฟล์ที่ "สะอาด" ดังนั้นคุณไม่ควรเชื่อใจ ความจริงที่ว่าปัญหาเหล่านี้หายากหรือผิดปกติจะไม่เปลี่ยนปัญหา โดยเฉพาะอย่างยิ่งมีวิธีแก้ปัญหาที่ดีกว่าสำหรับเรื่องนี้
bfontaine

lsอาจรวมถึงไดเรกทอรีและบรรทัดว่าง ฉันอยากจะแนะนำบางสิ่งบางอย่างfind . -type f | shuf -n10แทน
cherdt

9

วิธีง่ายๆในการเลือก5ไฟล์แบบสุ่มในขณะที่หลีกเลี่ยงที่จะสั่ง ls แจง นอกจากนี้ยังทำงานกับไฟล์ที่มีช่องว่างบรรทัดใหม่และอักขระพิเศษอื่น ๆ :

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

แทนที่echoด้วยคำสั่งที่คุณต้องการเรียกใช้สำหรับไฟล์ของคุณ


1
ทีนี้ท่อ + readไม่มีปัญหาเหมือนกับการวิเคราะห์คำlsใช่ไหม คือมันจะอ่านทีละบรรทัดดังนั้นจึงไม่สามารถใช้งานกับไฟล์ที่มีการขึ้นบรรทัดใหม่ในชื่อของพวกเขาได้
Ciprian Tomoiagă

3
คุณพูดถูก โซลูชันก่อนหน้าของฉันไม่ทำงานสำหรับชื่อไฟล์ที่มีการขึ้นบรรทัดใหม่และอาจแบ่งกับคนอื่นที่มีอักขระพิเศษเช่นกัน ฉันได้อัปเดตคำตอบเพื่อใช้การยกเลิกแบบ null แทนการขึ้นบรรทัดใหม่
scai

4

หากคุณติดตั้ง 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

สิ่งนี้จะไม่ทำงานหากชื่อไฟล์ของคุณมีการขึ้นบรรทัดใหม่
bfontaine

4

นี่คือการตอบกลับในภายหลังของ @ gniourf_gniourf คำตอบสุดท้ายซึ่งฉันเพิ่ง upvoted เพราะเป็นคำตอบที่ดีที่สุด (ครั้งเดียวเพื่อหลีกเลี่ยงevalและอีกครั้งสำหรับการจัดการชื่อไฟล์อย่างปลอดภัย)

แต่ฉันใช้เวลาสองสามนาทีในการแก้ให้หาย "คุณสมบัติ" ไม่ดีมาก "คำตอบนี้ใช้ หากทักษะการทุบตีของคุณแข็งแกร่งพอที่คุณจะเห็นได้ทันทีว่ามันทำงานอย่างไรให้ข้ามความคิดเห็นนี้ แต่ฉันไม่ได้และไม่มีการพันกันฉันคิดว่ามันคุ้มค่าที่จะอธิบาย

คุณสมบัติ # 1เป็นไฟล์ของเชลล์ a=(*)สร้างอาร์เรย์$aซึ่งสมาชิกเป็นไฟล์ในไดเรกทอรีปัจจุบัน Bash เข้าใจถึงความแปลกประหลาดทั้งหมดของชื่อไฟล์เพื่อให้รายชื่อนั้นรับประกันว่าถูกต้องรับประกันว่าจะถูกลบหนี ฯลฯ ไม่จำเป็นต้องกังวลเกี่ยวกับการแยกวิเคราะห์ชื่อไฟล์ที่ถูกส่งกลับโดยlsไม่จำเป็นต้องกังวลเกี่ยวกับการถูกต้องแยกชื่อไฟล์ต้นฉบับเดิมกลับโดย

Feature # 2คือการขยายพารามิเตอร์ Bash สำหรับอาร์เรย์หนึ่งอันซ้อนกันภายใน สิ่งนี้เริ่มต้นด้วย${#ARRAY[@]}ซึ่งขยายไปตามความยาวของ$ARRAYซึ่งจะขยายความยาวของ

การขยายตัวนั้นจะถูกใช้เพื่อห้อยอาร์เรย์ วิธีมาตรฐานในการหาตัวเลขสุ่มระหว่าง 1 ถึง N คือการหาค่าของโมดูโลตัวเลขสุ่มเราต้องการตัวเลขสุ่มระหว่าง 0 ถึงความยาวของอาร์เรย์ นี่คือวิธีการแบ่งออกเป็นสองบรรทัดเพื่อประโยชน์ของความชัดเจน:

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

แต่วิธีนี้ทำได้ในบรรทัดเดียวโดยลบการกำหนดตัวแปรที่ไม่จำเป็นออก

คุณสมบัติที่ 3คือการขยาย Bash ปีกกาถึงแม้ว่าฉันต้องยอมรับว่าฉันไม่เข้าใจทั้งหมด การขยายตัวรั้งถูกนำมาใช้เช่นการสร้างรายชื่อของ 25 ไฟล์ชื่อfilename1.txt, filename2.txtฯลฯecho "filename"{1..25}".txt":

การแสดงออกภายใน subshell ด้านบน"${a[RANDOM%${#a[@]}]"{1..42}"}"ใช้เคล็ดลับนั้นในการสร้างการขยาย 42 แบบแยกกัน การขยายรั้งวางหลักเดียวในระหว่าง]และ}ซึ่งตอนแรกฉันคิดว่ากำลังห้อยแถว แต่ถ้าเป็นเช่นนั้นมันจะถูกนำหน้าด้วยโคลอน (มันจะได้คืน 42 รายการติดต่อกันจากจุดสุ่มในอาเรย์ซึ่งไม่เหมือนกับการส่งคืน 42 ไอเท็มแบบสุ่มจากอาเรย์) ฉันคิดว่ามันเป็นเพียงการทำให้เชลล์รันการขยาย 42 เท่าดังนั้นการคืนค่า 42 รายการสุ่มจากอาร์เรย์ (แต่ถ้ามีใครสามารถอธิบายได้อย่างเต็มที่ฉันชอบที่จะได้ยินมัน)

เหตุผลที่ N ต้องฮาร์ดโค้ด (ถึง 42) คือการขยายรั้งเกิดขึ้นก่อนการขยายตัวแปร

สุดท้ายนี่คือคุณสมบัติ # 4หากคุณต้องการทำแบบนี้ซ้ำสำหรับลำดับชั้นไดเรกทอรี:

shopt -s globstar
a=( ** )

สิ่งนี้จะเปิดใช้ตัวเลือกเชลล์ที่ทำให้เกิด**การจับคู่ซ้ำ ตอนนี้$aอาร์เรย์ของคุณมีทุกไฟล์ในลำดับชั้นทั้งหมด


2

หากคุณมีไฟล์อื่น ๆ ในโฟลเดอร์ของคุณคุณสามารถใช้คำสั่งดังต่อประปาผมพบว่าในยูนิกซ์stackexchange

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

นี่ฉันต้องการที่จะคัดลอกไฟล์ cpแต่ถ้าคุณต้องการที่จะย้ายไฟล์หรือทำอย่างอื่นเพียงแค่เปลี่ยนคำสั่งสุดท้ายที่ฉันได้ใช้


1

นี่เป็นสคริปต์เดียวที่ฉันสามารถเล่นได้ดีกับ bash ใน MacOS ฉันรวมและแก้ไขโค้ดจากลิงก์สองลิงก์ต่อไปนี้:

คำสั่ง ls: ฉันจะรับรายการเต็มเส้นทางแบบเรียกซ้ำได้หนึ่งบรรทัดต่อไฟล์ได้อย่างไร

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

MacOS ไม่มีคำสั่งsort -Rและshufดังนั้นฉันต้องการ bash เฉพาะโซลูชันที่สุ่มไฟล์ทั้งหมดโดยไม่ซ้ำกันและไม่พบที่นี่ โซลูชันนี้คล้ายกับโซลูชันของ gniourf_gniourf # 4 แต่หวังว่าจะเพิ่มความคิดเห็นที่ดีขึ้น

สคริปต์ควรง่ายต่อการแก้ไขเพื่อหยุดหลังจาก N ตัวอย่างโดยใช้ตัวนับด้วยถ้าหรือ gniourf_gniourf สำหรับลูปที่มี 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

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;

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.