Làm cách nào tôi có thể chọn các tệp ngẫu nhiên từ một thư mục trong bash?


144

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 Ntệ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?


1
Cũng là một câu trả lời hay tại Unix & Linux: unix.stackexchange.com/a/38344/24170
Nikana Reklawyks


Câu trả lời:


180

Đâ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

Thật tuyệt, không biết sắp xếp -R; Tôi đã sử dụng bogosort trước đây :-p
alex

5
sort: tùy chọn không hợp lệ - R Hãy thử `sort --help 'để biết thêm thông tin.

2
Dường như không hoạt động đối với các tệp có không gian trong đó.
Houshalter 17/03/2017

Điều này sẽ làm việc cho các tệp có không gian (các đường xử lý đường ống). Nó không hoạt động cho tên với dòng mới trong đó. Chỉ sử dụng "$file", không được hiển thị, sẽ nhạy cảm với không gian.
Yann Vernier


108

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=COUNTgiá 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

4
OP muốn chọn Ncác tệp ngẫu nhiên, vì vậy sử dụng 1là một chút sai lệch.
aioobe

4
Nếu bạn có tên tập tin với dòng mới:find dirname -type f -print0 | shuf -zn1
Hitechcomputergeek

5
Nếu tôi phải sao chép các tệp được chọn ngẫu nhiên này vào một thư mục khác thì sao? Làm thế nào để thực hiện các thao tác trên các tập tin được chọn ngẫu nhiên?
Rishabh Agrahari

18

Dưới đây là một vài khả năng không phân tích đầu ra lsvà 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 randfvớ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à Ncầ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 evalvà 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ếpthự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.


Thứ nhất và thứ hai tạo ra "sự thay thế xấu"; nó không giống như "{1..42}"phần để lại dấu vết "1". Ngoài ra, $RANDOMchỉ có 15 bit và phương thức sẽ không hoạt động với hơn 32767 tệp để chọn.
Yann Vernier

13
ls | shuf -n 10 # ten random files

1
Bạn không nên dựa vào đầu ra của 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.
bfontaine

3
@bfontaine bạn có vẻ bị ám ảnh bởi dòng mới trong tên tệp :). Chúng có thực sự phổ biến không? Nói cách khác, có một số công cụ tạo tập tin với dòng mới trong tên của họ? Vì là người dùng nên rất khó để tạo một tên tệp như vậy. Tương tự đối với các tệp đến từ internet
Ciprian Tomoiagă

3
@CiprianTomoiaga Đó là một ví dụ về các vấn đề bạn có thể gặp phải. lskhô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.
bfontaine

lscó 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 -n10thay thế.
cherdt

9

Một giải pháp đơn giản để chọn 5cá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ế echobằng lệnh bạn muốn thực thi cho các tập tin của bạn.


1
tốt, không phải ống + readcó 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
Ciprian Tomoiagă

3
Bạn đúng rồi. Giải pháp trước đây của tôi không hoạt động đối với các tên tệp có chứa dòng mới và có thể phá vỡ các tên khác với các ký tự đặc biệt. Tôi đã cập nhật câu trả lời của mình để sử dụng kết thúc null thay vì dòng mới.
scai

4

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 Ntệ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

Điều này không hoạt động nếu tên tệp của bạn chứa dòng mới.
bfontaine

4

Đâ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 evalvà 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, $acó 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 # 2mở 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ố 3mở 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 ]}, 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ờ $amảng của bạn chứa mọi tệp trong toàn bộ phân cấp.


2

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.


1

Đâ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?

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 không có các lệnh sort -Rshuf , 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

0

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;

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.