bash의 디렉토리에서 임의의 파일을 어떻게 선택합니까?


144

약 2000 개의 파일이있는 디렉토리가 있습니다. Nbash 스크립트 또는 파이프 명령 목록을 사용하여 임의의 파일 샘플을 선택하려면 어떻게 해야합니까?


1
또한 유닉스 및 리눅스에서 좋은 답변 : unix.stackexchange.com/a/38344/24170
Nikana Reklawyks


답변:


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 : invalid option-R 자세한 정보는`sort --help '를 시도하십시오.

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}"}" )

    이 기능은 잘 문서화되어 있지 않습니다.

  • 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에 정확히 답변하지는 않습니다.


제 1 및 제 2는 "나쁜 치환"을 생성 하였다; "{1..42}"후행을 남기는 부분이 마음에 들지 않았습니다 "1". 또한 $RANDOM15 비트이며이 방법은 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

ls 구문 분석5피하면서 임의의 파일 을 선택하는 간단한 솔루션입니다 . 또한 공백, 개행 및 기타 특수 문자가 포함 된 파일과 함께 작동합니다.

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의 늦은 답변에 대한 심지어 나중에 답변입니다. eval안전한 파일 이름 처리 를 피하기 위해 한 번.

그러나이 답변에서 사용하는 "잘 문서화되지 않은"기능을 풀려면 몇 분이 걸렸습니다. Bash 기술이 충분히 작동하여 즉시 작동 방식을 확인한 경우이 설명을 건너 뛰십시오. 그러나 나는 그것을 풀지 않았고 그것을 풀었을 때 나는 그것이 가치가 있다고 생각합니다.

기능 # 1 은 쉘 자체의 파일 글 로빙입니다. 멤버가 현재 디렉토리의 파일 인 a=(*)배열을 만듭니다 $a. Bash는 파일 이름의 모든 기묘함을 이해하므로 목록이 정확하고 이스케이프 등을 보장합니다 ls.에서 반환 된 텍스트 파일 이름을 올바르게 구문 분석 할 필요가 없습니다 .

기능 # 2배열 에 대한 Bash 매개 변수 확장 입니다 . 이것은로 시작 하여 길이로 확장됩니다 .${#ARRAY[@]}$ARRAY

그런 다음 해당 확장을 사용하여 배열을 첨자 화합니다. 1과 N 사이의 난수를 찾는 표준 방법은 난수 modulo N의 값을 취하는 것입니다. 우리는 0과 배열의 길이 사이의 난수를 원합니다. 명확성을 위해 두 줄로 나눈 접근 방식은 다음과 같습니다.

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

그러나이 솔루션은 한 줄로 수행하여 불필요한 변수 할당을 제거합니다.

Feature # 3Bash brace expansion 이지만, 완전히 이해하지는 못하지만 고백해야합니다. 중괄호 확장라는 25 개 파일 목록 생성하기 위해, 예를 들어, 사용 filename1.txt, filename2.txt등, : echo "filename"{1..25}".txt".

위의 서브 쉘 내부의 표현식 "${a[RANDOM%${#a[@]}]"{1..42}"}"은 해당 트릭을 사용하여 42 개의 개별 확장을 생성합니다. 브레이스 팽창 사이의 한 자리에 배치 ]하고, }제 I의 생각에 배열 첨자하고,하지만, 그렇다면 그것은 콜론이 선행되어야한다. (또한 배열의 임의의 지점에서 42 개의 연속 항목을 반환했을 것입니다. 이는 배열에서 임의의 42 개의 항목을 반환하는 것과 전혀 다릅니다.) 쉘이 확장을 42 번 실행하여 반환하는 것으로 생각합니다. 배열에서 42 개의 랜덤 아이템. (그러나 누군가가 더 자세히 설명 할 수 있다면 듣고 싶습니다.)

N이 42까지 하드 코딩되어야하는 이유는 변수 확장 전에 가새 확장이 발생하기 때문입니다.

마지막으로 디렉토리 계층 구조에 대해이 작업을 반복적으로 수행하려면 기능 # 4 가 있습니다.

shopt -s globstar
a=( ** )

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

이것은 MacOS에서 bash로 멋지게 플레이 할 수있는 유일한 스크립트입니다. 다음 두 링크에서 스 니펫을 결합하고 편집했습니다.

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 -Rshuf 명령이 없으므로 중복없이 모든 파일을 무작위로 추출 하고 여기서 찾지 못한 bash 전용 솔루션이 필요했습니다 . 이 솔루션은 gniourf_gniourf의 솔루션 # 4와 유사하지만 더 나은 주석을 추가하기를 바랍니다.

스크립트는 N이있는 카운터를 사용하거나 N이있는 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

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.