我可以使用此命令选择一个随机文件
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
在zsh
shell 中,你可以这样做
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
笔记:
假设文件没有被删除。如果它们是并且inode
s 被重用,那么它们必须从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
- @使用
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
在这里它将跟踪打乱的文件。
- @使用
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
仅查找新文件而不是重新扫描所有文件