bash를 사용하여 폴더에서 중복되지 않은 임의 파일을 선택하는 방법은 무엇입니까?

bash를 사용하여 폴더에서 중복되지 않은 임의 파일을 선택하는 방법은 무엇입니까?

이 명령을 사용하여 임의의 파일을 선택할 수 있습니다

find ./ -type f | shuf -n 1

하지만 때로는 동일한 파일이 표시되기도 합니다.
중복된 파일 선택을 중지할 수 있나요?
이 작업을 위한 다른 유틸리티가 있습니까?

재귀 하위 폴더가 있을 수 있는 폴더에 약 50,000개의 txt 파일이 있고 이를 보기 위해 임의의 파일을 선택하고 싶지만 다시 보고 싶지 않습니다. + 매일 새 파일이 폴더에 추가됩니다...

답변1

코드의 문제는 새 경로 이름을 선택하기 위해 매번 목록을 다시 생성한다는 것입니다. 목록이 생성된 디렉토리에 동일한 파일을 유지하는 한 계속해서 동일한 경로 이름을 제공할 것입니다.

가끔 스크립트를 실행하면 간단한 대답은 다음과 같습니다.프로세스 파일 이동(또는 삭제). 이렇게 하면 다음에 스크립트를 실행하고 무작위 목록을 다시 생성할 때 처리된 파일이 목록에 포함되지 않습니다.

예를 들어 모든 파일이 디렉토리 안이나 아래에 있다고 가정하면 $HOME/newfiles다음 명령은 파일을 선택한 다음 다음으로 이동합니다 $HOME/oldfiles.

myfile=$( find "$HOME/newfiles" -type f -print0 | shuf -z -n 1 )

# use "$myfile" here

# later... move "$myfile" to somewhere else:
mv "$myfile" "$HOME/oldfiles"

이 답변의 나머지 부분에서는 동일한 스크립트 호출에서 임의의 경로 이름을 반복하려는 경우를 다룹니다.


파일과 디렉토리에 새 줄이 포함되어 있지 않다고 가정하면 Jeff Schaller가 표시됩니다.댓글로 추천해주세요:

find ./ -type f | shuf |
while IFS= read -r pathname; do
    # do work with "$pathname"
done

shuf앞서 언급한 것처럼 계층 구조의 경로 이름에 개행 문자가 포함되어 있지 않은 경우( 이 경우 이름이 엉망이 됩니다) 이는 현재 디렉토리 안이나 아래의 일반 파일에 대한 임의의 경로 이름을 제공합니다 .

안전한 변형은 null로 끝나는 목록으로 목록을 섞는 것입니다.

readarray -t -d '' pathnames < <( find . -type f -print0 | shuf -z )
for pathname in "${pathnames[@]}"; do
    # use "$pathname" here
done

이 예(및 다음 예)는https://unix.stackexchange.com/a/543188/116858


zsh쉘 에서는 할 수 있습니다

for pathname in ./**/*(.DNnoe['REPLY=$RANDOM'])
do
   # use $pathname here
done

이는 쉘 글롭을 사용하고 줄 기반 텍스트 필터링 기능이 없기 때문에 파일 이름에 줄 바꿈이 문제가 되지 않는다는 점을 제외하면 위의 코드와 유사합니다. 목록) .

이것의 좋은 점은 zsh외부 도구를 호출할 필요가 없다는 것입니다.

답변2

질문을 올바르게 이해했다면 OP가 할 수 있는 한 가지는 목록을 파일(또는 스크립트의 경우 변수 BASH)로 섞은 다음 해당 목록에서 요소를 추출하는 것입니다. 이렇게 하면 OP는 전체 목록이 끝날 때까지 동일한 파일을 두 번 호출하지 않습니다.

예를 들어,

find ./ -type f | shuf > shuffled.txt

파일에 목록을 생성한 후 다음과 같이 호출하세요.

cat shuffled.txt | head -1 | tail -1
cat shuffled.txt | head -2 | tail -1
cat shuffled.txt | head -3 | tail -1
...

sed또는 또는 와 동등한 라인입니다 awk.

BASH또는 이 모든 내용을 스크립트 에 넣으면 다음과 같이 할 수 있습니다.

for filename in $(find ./ -type f | shuf)
do
    echo ${filename}
    ... do something to ${filename}
done

답변3

inode같이 일하는 건 어때요...?

[[ ! -f seen ]] && touch seen && ls -i seen > seen                       
file=$(find . -type f -printf %i"\n" | sort | join -j 1 -v 1 - seen | shuf -n 1)
echo $file >> seen
sort -o seen seen
find -inum $file -exec cat {} \; #or whatever you want to do with the file

파일이 검색 경로에 있는지 여부는 중요하지 않습니다 seen. 만약 그렇다면 파일 inode자체에 추가하여 필터링하면 됩니다.

단일 검사 세션의 경우 목록을 반복하면 됩니다.

[[ ! -f seen ]] && touch seen && ls -i seen > seen
sort -o seen seen
list=$(mktemp)                        
find . -type f -printf %i"\n" | sort | join -j 1 -v 1 - seen | shuf -o $list
while read file; do
    echo $file >> seen
    find -inum $file -exec sh -c 'echo -e "$1 contains ....\n"; cat "$1"; echo -e "\n\n"' sh {} \;
    sleep 1
done < $list

노트: 파일이 삭제되지 않았다고 가정합니다. 재사용 되는 경우 inode다음에서 변경해야 합니다.seen

sed이 방법은 파일이 복사되고 덮어씌워지고 변경된 사실이 밝혀지면서 더욱 복잡해졌습니다 inode. seen삭제 문제에 대한 해결책 edsed.

파일 삭제touch wood

d="touch wood"; find . -iname "$d" -printf %i"\n%p\n" | while read i ; do read f; rm "$f" ;printf "%s\n" "/$i/d" wq | ed -s seen; done;

답변4

  1. @사용find
find ./ -type f | shuf |
while IFS= read -r pathname; do
    if ! grep -xF "$pathname" ~/shuffled.txt; then
      # do work with "$pathname"
      echo "$pathname" >> ~/shuffled.txt
    fi
done

여기에서는 스크램블된 파일을 추적합니다.

  1. @사용mlocate

사용할 때마다 find시간이 더 걸립니다... 대신 여기에서 mlocate 유틸리티를 사용하는 것이 더 좋습니다...

#!/bin/bash
set -e
sudo updatedb -U ./ -o mlocate.db && locate -d mlocate.db '*' | shuf |
while IFS= read -r pathname; do
  if [ -f "$pathname" ]; then
    if ! grep -xF "$pathname" ~/shuffled.txt; then
      # do work with "$pathname"
      echo "$pathname" >> ~/shuffled.txt
    fi
  fi
done

이 방법은 updatedb모든 파일을 다시 검색하는 대신 새 파일만 찾습니다.

관련 정보