Reputation: 41670
I've written a bash script that needs to do something later. It's called something like this:
later mv *.log /somewhere/else
However, when called like this *.log
is expanded at call time, and the script is called as if I wrote
later mv 1.log 2.log 3.log /somewhere/else
I'm trying to get my script to expand wildcards later. I tried calling it like this
later mv '*.log' /somewhere/else
and also with \*
, but these both result in the wildcard never getting expanded at all.
How do I expand the wildcards in the commandline? And is there a way to prevent expansion when the script is called, i.e. get the original parameters as they were typed?
This is the part of my scripts that prepares the call for later:
tmpfile=$(mktemp)
### Get a quoted commandline
line=
while (( "$#" > 0 )); do
line="$line\"$1\" "
shift
done
### Prepare a script to be run
echo '#!/bin/bash' > "$tmpfile"
echo "cd $(pwd)" >> "$tmpfile"
echo "trap 'rm \"$tmpfile\"' EXIT" >> "$tmpfile"
echo "$line" >> "$tmpfile"
chmod 777 "$tmpfile"
Note that I have to quote the commandline as some of my files and folders have spaces in their names; if I remove the quoting bit, even bits without wildcards stop working.
Upvotes: 6
Views: 2553
Reputation: 1383
In shell scripting there are bunch of tricky characters. Space and both quotes are the most difficult to handle. This suggested solution will address the problem of tricky characters by applying proper quoting.
#!/bin/bash
if [ $# -lt 1 ]; then
echo "Need argument!"
exit 1
fi
### Get a quoted commandline
line=
files=($(echo "$1"))
for file in "${files[@]}"; do
echo "Debug: Processing file [$file]"
quoted_filename=$(printf '%q' "$file")
if [ -n "$line" ]; then
line="$line "
fi
line="$line$quoted_filename"
done
echo -e "Result:\n$line"
Preparing test environment with four matching entries having potential pitfalls:
mkdir 'foo.bar.*'
date > 'foo.bar test.dat'
touch 'foo."nickname".bar'
touch "foo.'single'.bar"
Using suggested wildcard from question.
./later.sh 'foo*'
Debug: Processing file [foo.bar.*]
Debug: Processing file [foo.bar test.dat]
Debug: Processing file [foo."nickname".bar]
Debug: Processing file [foo.'single'.bar]
Result:
foo.bar.\* foo.bar\ test.dat foo.\"nickname\".bar foo.\'single\'.bar
Upvotes: 0
Reputation: 13934
The simplest way may be to simply pass the wildcard (glob) back to the shell for expansion,
for example:
#!/bin/bash
echo "For the wildcard $1"
echo "I find these match(es): $(eval echo "$1")"
The $( … )
operator calls a sub-shell, and interpolates the results; the echo
command simply returns them.
eval
is neccesary within a shell script (but not when keying it in on an interactive shell, in which case $(echo $1)
works. Note that you'll still have to escape spacing and the like, but it seems you've taken that into consideration. Naturally, the glob will need to be quoted when calling your script — this is why most Unix commands only take one list in the first or last position(s), although examples like find
do exist that require escaped fileglobs, themselves.
PS: As for a program forcing the shell to not perform expansion: no, there's no built-in way to do so. The shell expands globs before (well, logically “before” if not necessarily chronologically) even identifying what file your program lives in, and is pretty well universally agnostic to what programs it runs.
Upvotes: 1
Reputation: 14464
Interesting question. I suspect that a better answer than mine is possible. Nevertheless, given
A='foo.*'
this seems to do what you want:
echo $(eval echo "$A")
Upvotes: 5