2

I've checked some answers online, but they do not work when there's a -name parameter in the command (maybe I'm doing something wrong, not sure). What I'd like to do is to list only filenames (not the directory/path), and to complicate more, I have same filename with different extension, so I need to filter the result using -name and -o, the trouble is the suggestions that I saw use either -printf or basename, and those doesn't work well when there's -name *.txt -o name *.pdf. Is there any other way to solve this problem?

Example of my files:

/dir1/dir2/dir3/dir4/
                    /file1.txt
                    /file1.pdf
                    /file1.ods ...etc.

I'm only interested in listing one or more type of file per listing (by using -name to filter my results.

Is it possible to accomplish it using only 1 find command or I have to do it in 2 steps (saving the result in a temporary file, then filter/strip the directory from the temp file)? The result I'm trying to accomplish is a text file with filename per line:

file1.txt
file1.pdf
file1.ods
file2.txt
file2.pdf
etc.

The command I was using is

find /dir1/dir2/dir3/dir4/ -type f -name '*.txt' -o -name '*.pdf' -o -name '*.ods' -printf "%f\n"

I am using GNU find.

Update

It turned out I have to put \(...\), as reminded by αғsнιη

2

2 Answers 2

3

When using multiple expressions in logical OR mode with -o you need enclose them all within \( ... \), else the last expression result is always respected; and -printf '%f\n' is used to print the files' name only with any leading directories removed.

find . -type f \( -name '*.pdf' -o -name '*.txt' \) -printf '%f\n'

see man find under OPERATORS section about ( expr ):

OPERATORS
...

( expr )
Force precedence. Since parentheses are special to the shell, you will normally need to quote them.

so you can either use \( ... \) or '(' ... ')' (whitespaces after and before parenthesis are important) to escape them.

0
1

As has already been mentioned, you need to group your -name tests in parentheses. These parentheses need to be escaped as \( ... \) or quoted as e.g. '(' ... ')' to protect them from the shell (where they would otherwise designate a subshell).

Using standard find, and also making handling the filename suffixes slightly easier:

set -- txt pdf ods

for suffix do
    set -- "$@" -o -name "*.$suffix"
    shift
done
shift

find /dir1/dir2/dir3/dir4 \( "$@" \) -type f -exec basename {} \;

This first sets the positional parameters to the list of filename suffixes that we'd like to see (this could also be taken from the command line if this is a script), and constructs the OR-list of -name tests. This is then used in the find command line.

The the find command itself then calls the basename utility for each found name.

As an alternative, we could instead use a parameter substitution to remove all but the filename component of each pathname, but to do so we must pass the pathnames to an inline sh -c script:

# [...] as before [...]

find /dir1/dir2/dir3/dir4 \( "$@" \) -type f -exec sh -c '
    for pathname do
        printf "%s\n" "${pathname##*/}"
    done' sh {} +

I would expect this to run quicker than the variation using basename, at least if there are many filenames to handle, although with GNU basename from coreutils, you could use -a or --multiple likes so:

find /dir1/dir2/dir3/dir4 \( "$@" \) -type f -exec basename -a {} +

This would call basename as few times as possible. But then again, if you had coreutils installed, you may well have findutils too, with GNU find, and could use -printf '%f\n' to do the same thing.

1
  • Thanks αғsнιη and Kusalananda for your feedback and detailed explanation, it's much appreciated. With Linux, you never stops learning.
    – michaelbr
    Jan 7, 2021 at 19:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.