Bash script that calls a program with some asterisks in arguments

0

I’ve spent last hour or so trying to write a pretty simple bash script and I never felt so dumb.

So I have a list of strings (which are package selector specifications for a package manager, if that matters) which might contain asterisks. I need to build a command line preserving those asterisks and then call a program. Here is my naïve attempt:

xs="foo/bar */*"

xs_cmd=""
for x in $xs; do
  xs_cmd="$xs_cmd  -0 $x "
done

echo $xs_cmd

I need the echo call to be equivalent to

echo   -0 foo/bar    -0 '*/*' 

which outputs -0 foo/bar -0 */*.

P. S. In reality the first line is slightly more complex: xs="$some foo/bar */* $(get_others)".


If you run this script in a directory containing a/b, you’ll get -0 foo/bar -0 a/b instead.

When I change the last line to

echo "$xs_cmd"  # note the quotes

I get -0 foo/bar -0 a/b and, first of all that’s not what I want, since now I’m invoking echo with a single argument, and furthermore that tells me that the expansion is happening earlier, likely in the for loop.

But I can’t change the for line because

for x in "$xs"; do  # note the quotes

will just make the loop iterate only once: -0 foo/bar */* (there is a single -0) so adding those quotes is not an option for sure.

Now if I revert back to the original script and replace the first line with

xs="foo/bar '*/*'"  # note additional single quotes

I get -0 foo/bar -0 '*/*' (single quotes should’t be there).

It seems that I tried every combination of quotes and backslash-escapes of asterisks and I have literally no idea what to do now.

kirelagin

Posted 2014-10-27T12:08:38.870

Reputation: 2 664

Answers

0

I used your original script, but with single quotes on the first line (only) and it worked as I think you want it (-0 before each file path):-

xs='foo/bar */*'

xs_cmd=""
for x in $xs; do
  xs_cmd="$xs_cmd  -0 $x "
done

echo $xs_cmd

You will probably find problems when you come to use it if there are embedded blanks in any of the file paths. If so change the command in the loop to:-

  xs_cmd="$xs_cmd  -0 \"$x\" "

AFH

Posted 2014-10-27T12:08:38.870

Reputation: 15 470

Errr, no, this still performs expansion in the for loop and outputs a/b instead of */*. Another problem is that the first line is somewhat more complex in reality; I’ll update the question in a minute. – kirelagin – 2014-10-27T13:55:52.663

I thought you wanted it expanded in the for loop. I tested it on Ubuntu 14.04 and got -0 "foo/bar" -0 "dir/file" -0 "dir/file" ... – AFH – 2014-10-27T14:40:46.330

You might want to read my question again, especially the part right before the horizontal ruler that gives the desired output: no expansion should be performed; I need all the asterisks left in place. – kirelagin – 2014-10-27T15:13:00.213

OK. I have found something that works. Make your first line xs="foo/bar @/@", then after your for loop add the line xs_cmd=$(echo $xs_cmd|sed "s/@/\*/g"). If you can't find a character such as @ which is not in any of your file names, then use xs="foo/bar ///" and xs_cmd=$(echo $xs_cmd|sed "s-///-\*/\*-g"). I suggested using @ first as it is easier to generalise for other strings with asterisks. – AFH – 2014-10-27T16:28:26.393

So, now I have xs_cmd which is a string with some asterisks. How do I pass it to the command (echo in my example)? echo $xs_cmd will perform expansion. echo "$xs_cmd" will invoke echo with a single argument. – kirelagin – 2014-10-27T16:37:56.490

I mean, shell programming is truly insane. I could have written this program in any other language in like two minutes and make it work in just one attempt. But now I really want to know how to write this in shell! – kirelagin – 2014-10-27T16:41:36.420

Since you are fighting against file name expansion, why not disable it in this context with set -f and restore it afterwards with set +f? – AFH – 2014-10-27T18:37:07.467

Ah, of course! Well, I’d classify this as a workaround, not a true solution (because I’m still interested how to do this directly), but that works. Thank you! – kirelagin – 2014-10-28T06:52:45.303

I have been thinking about your comment, and I don't conclude it is a work-round. Your script fails because asterisks are being expanded when you don't want them to be, so stopping their expansion at the wrong time constitutes a solution, because it's precisely what the other proposals are trying to do. – AFH – 2014-10-28T18:06:09.717

0

Depending on what you're trying to achieve, your either need to quote these */*:

xs="foo/bar '*/*'" #!

xs_cmd=""
for x in $xs ; do
    xs_cmd="$xs_cmd  -0 $x "
done

echo $xs_cmd
eval echo $xs_cmd
eval "echo $xs_cmd"

or use arrays, or both:

xs=("foo/bar" "*/*") #!

xs_cmd=""
for x in "${xs[@]}"; do
    xs_cmd="$xs_cmd  -0 '$x' " #!
done

echo "$xs_cmd"
eval "echo $xs_cmd"

oxij

Posted 2014-10-27T12:08:38.870

Reputation: 1

As mentioned in my question, your first suggestion results in the following output: -0 foo/bar -0 '*/*', which means the quotes are literally passed to the program. Your second suggestion results in -0 'foo/bar' -0 'a/b' which both passes quotes to the program and performs expansion, while I need neither. – kirelagin – 2014-11-11T16:53:35.513