Как выбрать случайный файл из папки без повторения с помощью bash?

Попросите grepсамому построить список файлов, рекурсивно из текущего каталога:

grep -r MyPattern.

Это не совсем то же самое, что и *, так как он будет искать в подкаталогах -, но для почтовых каталогов это обычно то, что вам нужно.

0
23.11.2019, 07:28
3 ответа

Проблема с вашим кодом заключается в том, что вы -создаете список каждый раз, чтобы выбрать новый путь. Это потенциально может дать вам одни и те же пути снова и снова, пока вы храните одни и те же файлы в каталогах, для которых вы создаете список.

Простой ответ для случая, когда вы время от времени запускаете свой скрипт, состоит в том, чтобы переместить файлы процесса(или удалить их ). Таким образом, в следующий раз, когда вы запустите скрипт и повторно -создадите случайный список, уже обработанные файлы не будут частью списка.

Например, при условии, что все файлы расположены в каталоге $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"

Остальная часть этого ответа касается случая, когда вы хотите перебрать рандомизированные пути в одном и том же вызове скрипта.


Предполагая, что ваши файлы и каталоги не содержат встроенных символов новой строки, это показывает то, что предложил Джефф Шаллер в комментарии:

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

Это дало бы вам случайные пути к обычным файлам в текущем каталоге или ниже, если, как я уже упоминал, ни один из путей в иерархии не содержал новых строк (, и в этом случае shufзашифровывал бы эти имена ).

Безопасным вариантом было бы скремблировать список нулевым -завершенным списком:

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

Это работает аналогично приведенному выше коду с той разницей, что, поскольку здесь используется глобус оболочки и не используются инструменты фильтрации текста, -ориентированного на строку -, новые строки в именах файлов не будут проблемой (, и вы не будете не нужно обходить nul -завершенные списки ).

Удобство выполнения этого в zshзаключается в том, что вам не нужно вызывать какие-либо внешние инструменты.

3
28.01.2020, 02:14

Если я правильно понимаю вопрос, одна вещь, которую может сделать 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
2
28.01.2020, 02:14

Как насчет того, чтобы просто работать с 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, этот подход становится более сложным.... Решением проблемы удаления может быть использование ed, а не sed.

Чтобы удалить файл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;
1
28.01.2020, 02:14

Теги

Похожие вопросы