The following sh
script reads base names from the file names
, which contains one name per line (names should be quoted if they contain spaces etc.), and calls an in-line sh -c
script with batches of these (50 at a time). I'm dividing the input into batches, just in case the data expands to a list that is too long for a single invocation of find
(we need to construct commands with a total combined length of more than n
time the length of the input data, where n
is the number of filename suffixes to look for).
The in-line script constructs an "OR list" of -name
tests based on the given base names for find
. Each base name is entered into the list with variations for the three filenames suffixes .txt
, .csv
, and .py
.
The list is maintained in the list of positional parameters, "$@"
.
Once the list is complete, find
is called to find the regular files matching these names, in or under some directory $topdir
.
topdir=$HOME
<names xargs -L 50 sh -c '
topdir=$1; shift
for name do
for suffix in txt csv py; do
set -- "$@" -o -name "$name.$suffix"
done
shift # shift off current base name
done
shift # shift off the initial "-o"
find "$topdir" -type f \( "$@" \) -print
' sh "$topdir"
Run with a lower number than 50 and with sh -x -c
in place of sh -c
to see what commands the in-line script is actually executing.
If you want to use named arrays and the bash
shell:
topdir=$HOME
<names xargs -L 50 bash -c '
topdir=$1; shift
unset tests
for name do
for suffix in txt csv py; do
tests+=( -o -name "$name.$suffix" )
done
done
find "$topdir" -type f \( "${tests[@]:1}" \) -print
' bash "$topdir"
Here, an array, tests
, is used instead of the list of positional parameters. The strange-looking "${tests[@]:1}"
expands to the list of elements of the array, except for the first element (which will be -o
).
Although, if you're using bash
, you may just as well use its globbing facilities (originally inherited from the ksh
shell):
shopt -s extglob globstar dotglob nullglob
topdir=$HOME
printf -v pattern '%s/**/@(%s).@(txt|csv|py)' "$topdir" "$(paste -s -d '|' - <names)"
eval "pathnames=( $pattern )"
The following loop is only for illustration.
If you really just wanted to list the names, use
printf '%s\n' "${pathnames[@]}"
for pathname in "${pathnames[@]}"; do
printf '%s\n' "$pathname"
done
This constructs an extended globbing pattern from the contents of the names
file. This pattern may end up looking like
/home/myself/**/@(name1|name2|name3).@(txt|csv|py)
... which would match the names that you presumably are interested in. Note though that you would have to do any file type tests yourself in the loop (to sort out regular files from directories etc.)
The shell options set at the top of the script enables the use of the extend pattern @(...|...)
(extglob
), the use of **
to match down into subdirectories (globstar
), allows us to names that are hidden or located in hidden subdirectories (dotglob
). We also set nullglob
ta make the pattern disappear if there are no matches at all.