0

I am using find to return the list of files from a directory and its subdirectories:

find $directory -type f -name "*.epub"

Then I want to use a command that requires the input and output filenames to be specified:

ebook-convert input.epub output.txt

I want to convert each .epub to .txt such that the output is stored in the same directory/subdirectory as the input. Simple piping does not work.

find $directory -type f -name "*.epub" | ebook-convert

How do I do this?

  • 1
    Related: https://unix.stackexchange.com/questions/389705/understanding-the-exec-option-of-find and https://unix.stackexchange.com/questions/25921/how-can-i-run-a-specific-command-for-each-find-result/25938 – pLumo Aug 19 '19 at 09:22
  • surely that is the input file-name. – ctrl-alt-delor Aug 19 '19 at 09:29
  • @pLumo Thanks! I was unaware of exec. –  Aug 19 '19 at 09:30

1 Answers1

4

The trick is to not create a list of files that you then iterate over (see e.g. Why is looping over find's output bad practice?).

find "$directory" -type f -name '*.epub' -exec ebook-convert {} output.txt \;

This finds all regular files whose name matches *.epub in or below the $directory directory. For each, the ebook-convert command is executed with the pathname of the found file as its first argument and output.txt as its second.

This would obviously overwrite output.txt for each found file, but the following would work around that by creating a file with the same name as the original file with -converted.txt added to the end of the name (in the same directory as the original file):

find "$directory" -type f -name '*.epub' -exec ebook-convert {} {}-converted.txt \;

This may not work with all implementations of find as it may not replace the second {} with the pathname of the found file (since it's concatenated with another string; but e.g. GNU find handles it). To work around that:

find "$directory" -type f -name '*.epub' -exec sh -c '
    for pathname do
        ebook-convert "$pathname" "$pathname"-converted.txt
    done' sh {} +

With a shell, such as bash or zsh, that supports the ** globbing pattern:

for pathname in "$directory"/**/*.epub; do
    ebook-convert "$pathname" "$pathname"-converted.txt
done

(this requires shopt -s globstar in bash and will process any matching name, not just regular files, unless you use *.epub(.) in zsh or an explicit -f test in bash)

Kusalananda
  • 333,661
  • 1
    In the last example, use ${pathname%.*} to get rid of .epub extension. – pLumo Aug 19 '19 at 09:21
  • This is such a well-explained answer. One query though: what do you mean by "regular files"? –  Aug 19 '19 at 09:24
  • 1
    @user110327 A "regular file" is a file that is not a directory, socket, named pipe etc. It's a file type in UNIX. – Kusalananda Aug 19 '19 at 09:26
  • +1. also worth knowing about is find's -execdir action, which works just like -exec but runs the command from the same dir as the matching file(s). – cas Aug 19 '19 at 09:56