如何使用 bash 从文件夹中选择不重复的随机文件?

如何使用 bash 从文件夹中选择不重复的随机文件?

我可以使用此命令选择一个随机文件

find ./ -type f | shuf -n 1

但有时它会显示相同的文件。
是否可以停止选择重复的文件?
此任务还有其他实用程序吗?

我的文件夹中有大约 50k 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


zshshell 中,你可以这样做

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

这与上面的代码类似,不同之处在于,由于这是使用 shell glob 并且没有面向行的文本过滤工具,因此文件名中的换行符不会成为问题(并且您不必传递以 nul 结尾的列表) )。

这样做的巧妙之处在于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

笔记: 假设文件没有被删除。如果它们是并且inodes 被重用,那么它们必须从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;

答案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仅查找新文件而不是重新扫描所有文件

相关内容