ฉันมีไดเรกทอรีที่มีไฟล์ประมาณ 2,000 ไฟล์ ฉันจะเลือกตัวอย่างN
ไฟล์แบบสุ่มโดยใช้ bash script หรือรายการคำสั่ง piped ได้อย่างไร
ls | shuf -n 5
แหล่งที่มาจาก Unix Stackexchange
ฉันมีไดเรกทอรีที่มีไฟล์ประมาณ 2,000 ไฟล์ ฉันจะเลือกตัวอย่างN
ไฟล์แบบสุ่มโดยใช้ bash script หรือรายการคำสั่ง piped ได้อย่างไร
ls | shuf -n 5
แหล่งที่มาจาก Unix Stackexchange
คำตอบ:
นี่คือสคริปต์ที่ใช้ตัวเลือกการเรียงลำดับแบบสุ่มของ 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
"$file"
ไม่แสดงจะมีความอ่อนไหวต่อช่องว่าง
คุณสามารถใช้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
N
ไฟล์สุ่มดังนั้นการใช้จึง1
เป็นเรื่องเข้าใจผิดเล็กน้อย
find dirname -type f -print0 | shuf -zn1
นี่คือความเป็นไปได้บางอย่างที่ไม่แยกวิเคราะห์ผลลัพธ์ของ 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 ไฟล์ให้เลือก
ls | shuf -n 10 # ten random files
ls
คุณไม่ควรพึ่งพาการส่งออกของ สิ่งนี้จะไม่ทำงานหากเช่นชื่อไฟล์มีการขึ้นบรรทัดใหม่
ls
ไม่รับประกันว่าจะให้ชื่อไฟล์ที่ "สะอาด" ดังนั้นคุณไม่ควรเชื่อใจ ความจริงที่ว่าปัญหาเหล่านี้หายากหรือผิดปกติจะไม่เปลี่ยนปัญหา โดยเฉพาะอย่างยิ่งมีวิธีแก้ปัญหาที่ดีกว่าสำหรับเรื่องนี้
ls
อาจรวมถึงไดเรกทอรีและบรรทัดว่าง ฉันอยากจะแนะนำบางสิ่งบางอย่างfind . -type f | shuf -n10
แทน
วิธีง่ายๆในการเลือก5
ไฟล์แบบสุ่มในขณะที่หลีกเลี่ยงที่จะสั่ง ls แจง นอกจากนี้ยังทำงานกับไฟล์ที่มีช่องว่างบรรทัดใหม่และอักขระพิเศษอื่น ๆ :
shuf -ezn 5 * | xargs -0 -n1 echo
แทนที่echo
ด้วยคำสั่งที่คุณต้องการเรียกใช้สำหรับไฟล์ของคุณ
read
ไม่มีปัญหาเหมือนกับการวิเคราะห์คำls
ใช่ไหม คือมันจะอ่านทีละบรรทัดดังนั้นจึงไม่สามารถใช้งานกับไฟล์ที่มีการขึ้นบรรทัดใหม่ในชื่อของพวกเขาได้
หากคุณติดตั้ง 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
นี่คือการตอบกลับในภายหลังของ @ 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
อาร์เรย์ของคุณมีทุกไฟล์ในลำดับชั้นทั้งหมด
นี่เป็นสคริปต์เดียวที่ฉันสามารถเล่นได้ดีกับ bash ใน MacOS ฉันรวมและแก้ไขโค้ดจากลิงก์สองลิงก์ต่อไปนี้:
คำสั่ง ls: ฉันจะรับรายการเต็มเส้นทางแบบเรียกซ้ำได้หนึ่งบรรทัดต่อไฟล์ได้อย่างไร
#!/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 ไม่มีคำสั่ง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
ฉันใช้สิ่งนี้: มันใช้ไฟล์ชั่วคราว แต่ลึกลงไปในไดเรกทอรีจนกว่าจะพบไฟล์ปกติและส่งคืน
# 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;
เกี่ยวกับวิธีการแก้ปัญหา Perl เล็กน้อยจากนายกังที่นี่:
ฉันจะสับบรรทัดของไฟล์ข้อความในบรรทัดคำสั่ง Unix หรือในสคริปต์เปลือก?
$ ls | perl -MList :: Util = shuffle -e '@lines = shuffle (<>); พิมพ์ @lines [0..4] '