First big problem: $($2) $line
executes $2
by itself as a command, then tries to run its output (if any) as another command with $line
as an argument to it. You just want $2 $line
.
Second big problem: while read ... done < $(ls $1)
doesn't read from the list of filenames, it tries to the contents of a file specified by the output of ls -- this will fail in any number of ways depending on the exact circumstances. Process substitution (while read ... done < <(ls $1)
) would do more-or-less what you want, but it's a bash-only feature (i.e. you must start the script with #!/bin/bash
, not #!/bin/sh
). And anyway it's a bad idea to parse ls, you should almost always just use a shell glob (*
) instead.
The script also has some other potential issues with spaces in filenames (using $line
without double-quotes around it, etc), and weird stylistic oddities (you don't need ;
at the end of a line in shell). Here's my stab at a rewrite:
#! /bin/sh
if [ $# -ne 2 ]; then
echo "Usage: $0 <dir> <command to execute>"
exit 1
fi
for file in "$1"/*; do
$2 "$file"
done
echo "All done"
Note that I didn't put double-quotes around $2
. This allows you to specify multiword commands (e.g. ./myscript thisDir "cat -v"
would be interpreted as running the cat
command with the -v
option, rather than trying to run a command named "cat -v"
). It would actually be a bit more flexible to take all arguments after the first one as the command and its argument, allowing you to do e.g. ./myscript thisDir cat -v
, ./myscript thisDir grep -m1 "pattern with spaces"
, etc:
#! /bin/sh
if [ $# -lt 2 ]; then
echo "Usage: $0 <dir> <command to execute> [command options]"
exit 1
fi
dir="$1"
shift
for file in "$dir"/*; do
"$@" "$file"
done
echo "All done"