Shell – returning strings corresponding to shell glob matching

filenamesshellwildcards

Suppose I have a subdirectory called sub. Then I want to operate on all files in that directory with the extension "txt". I want something like

for f in sub/*.txt
do
    enscript -b "" -o {$f basename excluding .txt}.ps sub/{$f basename excluding .txt}.txt                                                                                                                             
    ps2pdf {$f basename excluding .txt}.ps {$f basename excluding .txt}.pdf
done

So if there was just one file in the sub directory of the form *.txt, say sub/foo.txt, the script should expand to

enscript -b "" -o foo.ps sub/foo.txt                                                                                                                                   
ps2pdf foo.ps foo.pdf

I generally prefer portable solutions, but it is always interesting to learn about different ways of doing things. So non-portable is Ok, please just mention whether your solution is portable or non-portable. Also, the title is probably not the best possible, so feel free to modify.

EDIT: I realized the example as written did not quite make sense, since foo.txt was in the sub directory, and therefore sub/foo.txt would need to be passed to enscript. I also changed the -p enscript option to the more standard -o option.

Best Answer

POSIX shells (e.g., bash, dash, ksh, …) accept a ${var%suffix} construct that can be used to remove the trailing portion from the value of a variable. For instance, if path="sub/file.txt", then ${path%.txt} expands to sub/file.

There's a symmetric construct to remove a prefix: ${var#prefix}. The prefix can be a pattern. Doubling the # strips the longest matching prefix. For example, if path=sub/dir/file.txt, then ${path##*/} expands to file.txt (and ${path#*/} expands to dir/file.txt). The same goes for suffixes and %.

So, in your case you could write this (note that you can't combine the prefix stripping and the suffix stripping into a single expansion, at least not with only POSIX constructs):

for f in sub/*.txt; do
  base=${f%.txt}
  base=${base##*/}
  enscript -b "" -o "$base.ps" "$f"
  ps2pdf "$base.ps" "$base.pdf"
done

Alternatively, the GNU basename command accepts an optional second argument which is the file extension to remove. For instance, if $f is file.txt, then $(basename $f .txt) expands to file.

Note, however, that basename removes all path information except for the last component, so if you want to remove just the extension you have to put that back (see the dirname command).