-3

In a shell script, if we want to loop over certain filenames, which we would get by shell globbing, as for example all MKV-files in all sub-directories starting with string "Example" – how can we do this without using eval?

E.g. while the following script does the loop

#!/usr/local/bin/zsh

for i in /media/mybook/Example*/; do;
  t="ls \"$i\"*.mkv" 
  s=$(eval $t)
  echo $s
done

is there a way to get rid of the eval?

viuser
  • 2,614
  • Regarding you recent edit: Any chance that we get to see what you're actually trying to do so that we can modify our solutions? – Kusalananda Feb 05 '19 at 21:18

2 Answers2

1

I'm confused why you would want to use eval here. You could just write s=$(ls "$i"*.mkv). But calling ls is pointless and would mangle file names. Just iterate over the files normally.

for dir in /media/mybook/Example*/; do;
  if ! [ -d "$dir" ]; then continue; fi
  for file in "$dir"/*.mkv; do
    if ! [ -e "$file" ]; then continue; fi
    echo "$file"
  done
done

Note how "$dir" is within double quotes (so that any special characters such as spaces in the directory name remain as they are), but the * is outside the quotes so it's treated as a wildcard.

The lines with continue are there to skip the special case where the wildcard matches nothing. In sh, when a wildcard doesn't match, it's left as is, and so for sees a list of names with one element which is literally /media/mybook/Example*/ (for the outer loop). Some shells (ksh, bash, zsh) have a way to avoid this, for example in bash:

shopt -s nullglob
for dir in /media/mybook/Example*/; do;
  for file in "$dir"/*.mkv; do
    echo "$file"
  done
done

If you're just processing the files and don't need to do anything for the directories, there's no point in having two nested loops.

for dir in /media/mybook/Example*/*.mkv; do
  if ! [ -e "$file" ]; then continue; fi
  echo "$file"
done

All these snippets act on files inside the Example* directories themselves, no in their subdirectories. If you want to traverse the directories recursively, see Kusalananda's answer.

0

Using find:

find /media/mybook/Example*/ -type f -name '*.mkv' -print

Add -maxdepth 1 before -type f if you don't want to recurse into subdirectories.

Or just a straight shell loop (will not work if there are many thousands of files), and assuming you only want to look in the first subdirectory level:

for pathname in /media/mybook/Example*/*.mkv; do
    printf '%s\n' "$pathname"
done

If you want to recurse down into subdirectories, using zsh or bash with its globstar shell option set (again, will not work for several thousand files):

for pathname in /media/mybook/Example*/**/*.mkv; do
    printf '%s\n' "$pathname"
done

Related:


To do something with the pathnames:

With find:

find /media/mybook/Example*/ -type f -name '*.mkv' -exec sh -c '
    for pathname do
        # Use "$pathname" here
    done' sh {} +

... where sh -c could obviously be changed to bash -c or zsh -c if you need to use any special features of these shells.

Related:

With a shell loop:

for pathname in /media/mybook/Example*/*.mkv; do
    # Use "$pathname" here
done

Also related:

Kusalananda
  • 333,661