Tôi có một thư mục với khoảng 2000 tập tin. Làm cách nào tôi có thể chọn một mẫu N
tệp ngẫu nhiên thông qua sử dụng tập lệnh bash hoặc danh sách các lệnh được đặt?
ls | shuf -n 5
Nguồn từ Unix Stackexchange
Tôi có một thư mục với khoảng 2000 tập tin. Làm cách nào tôi có thể chọn một mẫu N
tệp ngẫu nhiên thông qua sử dụng tập lệnh bash hoặc danh sách các lệnh được đặt?
ls | shuf -n 5
Nguồn từ Unix Stackexchange
Câu trả lời:
Đây là tập lệnh sử dụng tùy chọn ngẫu nhiên của GNU sort:
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"
, không được hiển thị, sẽ nhạy cảm với không gian.
Bạn có thể sử dụng shuf
(từ gói GNU coreutils) cho điều đó. Chỉ cần cung cấp cho nó một danh sách các tên tệp và yêu cầu nó trả về dòng đầu tiên từ một hoán vị ngẫu nhiên:
ls dirname | shuf -n 1
# probably faster and more flexible:
find dirname -type f | shuf -n 1
# etc..
Điều chỉnh -n, --head-count=COUNT
giá trị để trả về số lượng dòng mong muốn. Ví dụ để trả về 5 tên tệp ngẫu nhiên bạn sẽ sử dụng:
find dirname -type f | shuf -n 5
N
các tệp ngẫu nhiên, vì vậy sử dụng 1
là một chút sai lệch.
find dirname -type f -print0 | shuf -zn1
Dưới đây là một vài khả năng không phân tích đầu ra ls
và an toàn 100% đối với các tệp có dấu cách và ký hiệu ngộ nghĩnh trong tên của chúng. Tất cả chúng sẽ điền vào một mảng randf
với một danh sách các tệp ngẫu nhiên. Mảng này dễ dàng in với printf '%s\n' "${randf[@]}"
nếu cần.
Cái này có thể sẽ xuất ra cùng một tệp nhiều lần và N
cần được biết trước. Ở đây tôi chọn N = 42.
a=( * )
randf=( "${a[RANDOM%${#a[@]}]"{1..42}"}" )
Tính năng này không phải là tài liệu rất tốt.
Nếu N không được biết trước, nhưng bạn thực sự thích khả năng trước đó, bạn có thể sử dụng eval
. Nhưng đó là điều xấu, và bạn phải thực sự chắc chắn rằng điều N
đó không đến trực tiếp từ đầu vào của người dùng mà không được kiểm tra kỹ lưỡng!
N=42
a=( * )
eval randf=( \"\${a[RANDOM%\${#a[@]}]\"\{1..$N\}\"}\" )
Cá nhân tôi không thích eval
và do đó câu trả lời này!
Tương tự bằng cách sử dụng một phương pháp đơn giản hơn (một vòng lặp):
N=42
a=( * )
randf=()
for((i=0;i<N;++i)); do
randf+=( "${a[RANDOM%${#a[@]}]}" )
done
Nếu bạn không muốn có thể có nhiều lần cùng một tệp:
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
Lưu ý . Đây là một câu trả lời muộn cho một bài viết cũ, nhưng câu trả lời được chấp nhận liên kết đến một trang bên ngoài cho thấy khủng khiếpbashthực hành, và câu trả lời khác không tốt hơn nhiều vì nó cũng phân tích đầu ra của ls
. Một nhận xét cho câu trả lời được chấp nhận chỉ ra một câu trả lời xuất sắc của Lhunath, điều rõ ràng cho thấy thực tiễn tốt, nhưng không trả lời chính xác cho OP.
"{1..42}"
phần để lại dấu vết "1"
. Ngoài ra, $RANDOM
chỉ có 15 bit và phương thức sẽ không hoạt động với hơn 32767 tệp để chọn.
ls | shuf -n 10 # ten random files
ls
. Điều này sẽ không hoạt động nếu ví dụ một tên tệp chứa dòng mới.
ls
không được đảm bảo để cung cấp cho bạn tên tệp "sạch" vì vậy bạn không nên dựa vào nó. Thực tế là những vấn đề này rất hiếm hoặc bất thường không làm thay đổi vấn đề; đặc biệt là có những giải pháp tốt hơn cho việc này.
ls
có thể bao gồm các thư mục và dòng trống. Tôi sẽ đề nghị một cái gì đó như find . -type f | shuf -n10
thay thế.
Một giải pháp đơn giản để chọn 5
các tệp ngẫu nhiên trong khi tránh phân tích ls . Nó cũng hoạt động với các tệp chứa khoảng trắng, dòng mới và các ký tự đặc biệt khác:
shuf -ezn 5 * | xargs -0 -n1 echo
Thay thế echo
bằng lệnh bạn muốn thực thi cho các tập tin của bạn.
read
có vấn đề tương tự như phân tích cú pháp ls
? cụ thể là, nó đọc từng dòng một, vì vậy nó không hoạt động đối với các tệp có dòng mới trong tên của chúng
Nếu bạn đã cài đặt Python (hoạt động với Python 2 hoặc Python 3):
Để chọn một tệp (hoặc dòng từ một lệnh tùy ý), sử dụng
ls -1 | python -c "import sys; import random; print(random.choice(sys.stdin.readlines()).rstrip())"
Để chọn N
tệp / dòng, sử dụng (lưu ý N
ở cuối lệnh, thay thế bằng một số)
ls -1 | python -c "import sys; import random; print(''.join(random.sample(sys.stdin.readlines(), int(sys.argv[1]))).rstrip())" N
Đây là một phản hồi thậm chí muộn hơn cho câu trả lời muộn của @ gniourf_gniourf, mà tôi vừa nêu lên vì đó là câu trả lời hay nhất, gấp đôi. (Một lần để tránh eval
và một lần để xử lý tên tệp an toàn.)
Nhưng tôi đã mất vài phút để gỡ rối các tính năng "không được ghi chép rõ ràng" mà câu trả lời này sử dụng. Nếu các kỹ năng Bash của bạn đủ vững chắc để bạn thấy ngay cách nó hoạt động, thì hãy bỏ qua nhận xét này. Nhưng tôi đã không làm, và gỡ rối nó tôi nghĩ rằng nó đáng để giải thích.
Tính năng số 1 là tập tin toàn cầu của shell. a=(*)
tạo một mảng, $a
có thành viên là các tệp trong thư mục hiện tại. Bash hiểu tất cả những điều kỳ lạ của tên tệp, vì vậy danh sách đó được đảm bảo chính xác, được bảo đảm thoát, v.v. Không cần phải lo lắng về việc phân tích đúng tên tệp văn bản được trả về ls
.
Tính năng # 2 là mở rộng tham số Bash cho mảng , cái này được lồng trong cái khác. Điều này bắt đầu với ${#ARRAY[@]}
, mở rộng theo chiều dài $ARRAY
.
Sự mở rộng đó sau đó được sử dụng để đăng ký mảng. Cách tiêu chuẩn để tìm một số ngẫu nhiên trong khoảng từ 1 đến N là lấy giá trị của số ngẫu nhiên modulo N. Chúng tôi muốn một số ngẫu nhiên nằm trong khoảng từ 0 đến độ dài của mảng. Đây là cách tiếp cận, được chia thành hai dòng cho rõ ràng:
LENGTH=${#ARRAY[@]}
RANDOM=${a[RANDOM%$LENGTH]}
Nhưng giải pháp này thực hiện nó trong một dòng duy nhất, loại bỏ việc gán biến không cần thiết.
Tính năng số 3 là mở rộng cú đúp của Bash , mặc dù tôi phải thú nhận rằng tôi không hoàn toàn hiểu nó. Mở rộng Brace được sử dụng, ví dụ, để tạo ra một danh sách các tập tin có tên 25 filename1.txt
, filename2.txt
, vv: echo "filename"{1..25}".txt"
.
Biểu thức bên trong lớp con bên trên "${a[RANDOM%${#a[@]}]"{1..42}"}"
, sử dụng thủ thuật đó để tạo ra 42 bản mở rộng riêng biệt. Việc mở rộng dấu ngoặc đặt một chữ số duy nhất ở giữa ]
và }
, lúc đầu tôi nghĩ là đăng ký mảng, nhưng nếu vậy nó sẽ được đặt trước dấu hai chấm. (Nó cũng đã trả về 42 mục liên tiếp từ một vị trí ngẫu nhiên trong mảng, điều này hoàn toàn không giống với việc trả lại 42 mục ngẫu nhiên từ mảng.) Tôi nghĩ rằng nó chỉ làm cho shell chạy mở rộng 42 lần, do đó trả về 42 mục ngẫu nhiên từ mảng. (Nhưng nếu ai đó có thể giải thích nó đầy đủ hơn, tôi rất muốn nghe nó.)
Lý do N phải được mã hóa cứng (đến 42) là việc mở rộng dấu ngoặc xảy ra trước khi mở rộng biến.
Cuối cùng, đây là Tính năng số 4 , nếu bạn muốn làm điều này một cách đệ quy cho hệ thống phân cấp thư mục:
shopt -s globstar
a=( ** )
Điều này bật một tùy chọn shell gây ra **
đệ quy khớp. Bây giờ $a
mảng của bạn chứa mọi tệp trong toàn bộ phân cấp.
Nếu bạn có nhiều tệp hơn trong thư mục của mình, bạn có thể sử dụng lệnh piped bên dưới mà tôi tìm thấy trong unix stackexchange .
find /some/dir/ -type f -print0 | xargs -0 shuf -e -n 8 -z | xargs -0 cp -vt /target/dir/
Ở đây tôi muốn sao chép các tập tin, nhưng nếu bạn muốn di chuyển tập tin hoặc làm một cái gì đó khác, chỉ cần thay đổi lệnh cuối cùng mà tôi đã sử dụng cp
.
Đây là kịch bản duy nhất tôi có thể chơi tốt với bash trên MacOS. Tôi đã kết hợp và chỉnh sửa đoạn trích từ hai liên kết sau:
Lệnh ls: làm thế nào tôi có thể nhận được một danh sách đường dẫn đệ quy, một dòng trên mỗi tệp?
#!/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 không có các lệnh sort -R và shuf , vì vậy tôi cần một giải pháp bash chỉ ngẫu nhiên tất cả các tệp mà không trùng lặp và không tìm thấy ở đây. Giải pháp này tương tự như giải pháp số 4 của gniourf_gniourf, nhưng hy vọng sẽ thêm ý kiến tốt hơn.
Tập lệnh phải dễ dàng sửa đổi để dừng sau N mẫu bằng cách sử dụng bộ đếm với if, hoặc gniourf_gniourf's cho vòng lặp với N. $ RANDOM bị giới hạn ở ~ 32000 tệp, nhưng điều đó sẽ phù hợp với hầu hết các trường hợp.
#!/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
Tôi sử dụng cái này: nó sử dụng tập tin tạm thời nhưng đi sâu vào một thư mục cho đến khi nó tìm thấy một tập tin thông thường và trả lại nó.
# 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;
Làm thế nào về một giải pháp Perl được tài liệu hóa một chút từ ông Kang ở đây:
Làm thế nào tôi có thể xáo trộn các dòng của tệp văn bản trên dòng lệnh Unix hoặc trong một tập lệnh shell?
$ ls | perl -MList :: Util = shuffle -e '@lines = shuffle (<>); in @lines [0..4] '