If you plan on using the filenames found by any of the various variations below, then make sure that you don't parse the filenames from the output. Instead, replace the printf
with whatever operation that you need to perform. This way, you avoid issues with filenames that contain whitespaces and globbing characters etc. It will also make your code more efficient and beautiful, and will eventually lead to a healthier lifestyle and nicer friends.
See Why is looping over find's output bad practice?
An assumption I'm making is that you'd like to find the JPEG images that does not have a .webp
file corresponding to it because you'd like to create the .webp
files. This is why I'm using an -e
("name exists") test below rather than a -f
("name exists and is a regular file or symbolic link to such file") or -r
("name exists and is readable") test.
If the .webp
name exists, and if it's a directory or a character special file or some other non-regular file type (the -f
test would be false), or if it's not readable (the -r
test would be false), it would be problematic to create the .webp
file as it would overwrite something existing, or be put into a directory, or cause a permission error, depending on how that .webp
file was created.
Using a simple loop that iterates over the names matching *.jpg
and prints those names out that don't correspond to a *.jpg.webp
file:
for name in *.jpg; do
[ -f "$name" ] && [ ! -e "$name.webp" ] && printf '%s\n' "$name"
done
To save the names in a list, we may use the list of positional parameters:
set --
for name in *.jpg; do
[ -f "$name" ] && [ ! -e "$name.webp" ] && set -- "$@" "$name"
done
printf 'Needs webp fixed: %s\n' "$@"
In bash
, you may want to save the names in a named array:
needs_webp=()
for name in *.jpg; do
[ -f "$name" ] && [ ! -e "$name.webp" ] && needs_webp+=( "$name" )
done
printf 'Needs webp fixed: %s\n' "${needs_webp[@]}"
In the zsh
shell, you could do the testing for the *.webp
name in the globbing itself:
printf 'Needs webp fixed: %s\n' *.jpg(.e['[ ! -e $REPLY.webp ]'])
Here, the .
in the globbing qualifier at the end of the pattern makes sure that only regular files (not directories etc.) are matched, and e['[ ! -e $REPLY.webp ]']
acts like a test to see whether the current name, $REPLY
, should be included in the resulting list or not.
With find
:
find . -name '*.jpg' -type f ! -exec test -e {}.webp \; -print
This looks for any regular file with a name matching the pattern *.jpg
in the current directory or below, and then prints that name if there is no file corresponding to the same name but with .webp
added to the end.
However, the POSIX standard does not guarantee that {}
is replaced by the current filename if it's concatenated with some other string, like it is above. So if you want to be certain it'll work, you'd say
find . -name '*.jpg' -type f -exec sh -c '
for name do
[ -e "$name.webp" ] && printf "%s\n" "$name"
done' {} +
As you see, now we're back at the first loop in this answer, more or less. The only difference now is that find
is feeding it existing pathnames, recursively from the current directory or any of its subdirectories.
-e
instead of-f
? Wouldn't that return directories as well? – schrodingerscatcuriosity Mar 10 '21 at 08:21somename.jpg.webp
does not exist (at all) butsomename.jpg
does, then one may imagine that the.webp
file should be created. If the.webp
name exists, but is a directory or some other non-regular file, then it would not be apropriate to try to create a file with that name. – Kusalananda Mar 10 '21 at 08:27.jpg
names are regular files (or symbolic links to such files). – Kusalananda Mar 10 '21 at 08:28