find
considers finding nothing a special case of success (no error occurred). A general way to test whether files match some find
criteria is to test whether the output of find
is empty. For better efficiency when there are matching files, use -quit
on GNU find to make it quit at the first match, or head
(head -c 1
if available, otherwise head -n 1
which is standard) on other systems to make it die of a broken pipe rather than produce long output.
while IFS= read -r name; do
[ -n "$(find . -name "$name" -print | head -n 1)" ] || printf '%s\n' "$name"
done <file_list
In bash ≥4 or zsh, you don't need the external find
command for a simple name match: you can use **/$name
. Bash version:
shopt -s nullglob
while IFS= read -r name; do
set -- **/"$name"
[ $# -ge 1 ] || printf '%s\n' "$name"
done <file_list
Zsh version on a similar principle:
while IFS= read -r name; do
set -- **/"$name"(N)
[ $# -ge 1 ] || print -- "$name"
done <file_list
Or here's a shorter but more cryptic way of testing the existence of a file matching a pattern. The glob qualifier N
makes the output empty if there is no match, [1]
retains only the first match, and e:REPLY=true:
changes each match to expand to 1
instead of the matched file name. So **/"$name"(Ne:REPLY=true:[1]) false
expands to true false
if there is a match, or to just false
if there is no match.
while IFS= read -r name; do
**/"$name"(Ne:REPLY=true:[1]) false || print -- "$name"
done <file_list
It would be more efficient to combine all your names into one search. If the number of patterns is not too large for your system's length limit on a command line, you can join all the names with -o
, make a single find
call, and post-process the output. If none of the names contain shell metacharacters (so that the names are find
patterns as well), here's a way to post-process with awk (untested):
set -o noglob; IFS='
'
set -- $(<file_list sed -e '2,$s/^/-o\
/')
set +o noglob; unset IFS
find . \( "$@" \) -print | awk -F/ '
BEGIN {while (getline <"file_list") {found[$0]=0}}
wanted[$0]==0 {found[$0]=1}
END {for (f in found) {if (found[f]==0) {print f}}}
'
Another approach would be to use Perl and File::Find
, which makes it easy to run Perl code for all the files in a directory.
perl -MFile::Find -l -e '
%missing = map {chomp; $_, 1} <STDIN>;
find(sub {delete $missing{$_}}, ".");
print foreach sort keys %missing'
An alternate approach is to generate a list of file names on both sides and work on a text comparison. Zsh version:
comm -23 <(<file_list sort) <(print -rl -- **/*(:t) | sort)
On a GNU system, you could do:
find /home/ -mtime +10 -type f -size +100M -delete -printf '%h\0' |
awk -v RS='\0' '!seen[$0]++ {out = out $0 RS}
END {printf "%s", out}' |
xargs -r0 rmdir
We use awk
to filter out duplicate while still keeping the order (leaves before the branch they're on) and also delay the printing until all the files have been removed so rmdir
can remove empty directories.
With zsh
:
files=(/home/**/*(D.LM+100m+10od))
rm -f $files
rmdir ${(u)files:h}
Note that those would remove the directories that become empty after files are removed from them, but not the parent of those directories if they don't have any of those files to delete and become empty as a result of the directories being removed. If you want to remove those as well, with GNU rmdir
, you can add the -p
/--parents
option to rmdir
.
If you wanted to remove all empty directories regardless of whether files or directories have been removed from them or not, still with GNU find
, you could do:
find /home/ \( -mtime +10 -type f -size +100M -o -type d -empty \) -delete
Best Answer
Another complicated option:
test.txt
's modification time (mtime
)"before delta" = now + hour - mtime
(assumingmtime
is in the past)"after delta" = now - hour - mtime if now - mtime > hour else 0
find -type f -mmin -"before delta" -mmin +"after delta"
It finds all files that are modified less than "before delta" minutes ago and greater than "after delta" minutes ago i.e., +/- hour around
test.txt
's modification time.It might be simpler to understand if you draw
now
,mtime
,"before"
,"after"
times on a line.date
command allows to getnow
andmtime
.As a one-liner: