0

I've been writing one-line bash scripts on the command line since 1989. These usually have the form:

for name in 1 2 3; do wc $name.txt; done

Now I'm trying to do some image manipulation with GIMP, using a script that takes two filenames for arguments. Here's something that gives me a valid command line:

/Applications/GIMP.app/Contents/MacOS/GIMP -b '(script-fu-overlay "png0004.tif" "png0000.tif") (script-fu-overlay "png0004.tif" "png0001.tif") (script-fu-overlay "png0004.tif" "png0002.tif") (script-fu-overlay "png0004.tif" "png0003.tif")'

It was generated with the help of a typical one-liner:

X=\'$(for name in 0 1 2 3; do echo \(script-fu-overlay \"png0004.tif\" \"png000$name.tif\"\) ; done)\'

But...and here is my question:

echo /Applications/GIMP.app/Contents/MacOS/GIMP -b $X

gives me the exact command line above, but when I run

/Applications/GIMP.app/Contents/MacOS/GIMP -b $X

I get errors about not being able to open filenames that have quotes around them, but when I run

/Applications/GIMP.app/Contents/MacOS/GIMP -b "$X"

everything works. My real goal is to understand whether it's possible for me to write a one-liner without resorting to variables at all. Something like:

/Applications/GIMP.app/Contents/MacOS/GIMP -b \'$(for name in 0 1 2 3; do echo \(script-fu-overlay \"png0004.tif\" \"png000$name.tif\"\) ; done)\'

But that fails in apparently the same way that

/Applications/GIMP.app/Contents/MacOS/GIMP -b $X

fails.

  • Change png000$name.tif to png000${name}.tif – mrc02_kr Jul 12 '17 at 13:43
  • This looks like a GIMP parsing issue, not a bash one. Try escaping the \ that surround your filenames: \\"png0004.tif\\" – Gohu Jul 12 '17 at 14:16
  • 1
    You answered your own question: -b "$X" works -b $X doesn't. Hence the $(....) needs to be quoted as well for it to be treated as one single entity to -b option. – user218374 Jul 12 '17 at 14:17
  • 1
    @Gohu may look like, but it's actually bash parsing, I'm quite sure. He composes a string that looks like the command line he wants to enter, but it will be treated differently, as quotes won't be evaluated at this stage. – Philippos Jul 12 '17 at 14:25
  • @Gohu The gimp doesn't know. The shell removes the single quotes and passes the whole string between them as one parameter to gimp. gimp doesn't know how this parameter has been composed. – Philippos Jul 12 '17 at 14:33
2

Yes you can do it on the command line without resorting to shell variables.

/Applications/GIMP.app/Contents/MacOS/GIMP -b "$(for name in 0 1 2 3; do echo \(script-fu-overlay \"png0004.tif\" \"png000$name.tif\"\) ; done)"

Note that we need to do away with the escaped single quotes since we are not doing an eval here. Only thing that is needed are the double quotes around the command expansion $(...) to prevent word split from happening PLUS pass the result as one single argument to gimp.

1

What about this:

$ /Applications/GIMP.app/Contents/MacOS/GIMP -b \
"$(printf '%s\n' '(script-fu-overlay "png0004.tif" "png'{0..0004}'.png")')"
1

man bash says:

The order of expansions is: brace expansion; tilde expansion, parameter and variable expansion, arithmetic expansion, and command substitution (done in a left-to-right fashion); word splitting; and pathname expansion [...] After these expansions are performed, quote characters present in the original word are removed unless they have been quoted themselves (quote removal).

Thus, only quotes from the original word are removed. But those quotes of yours appear only after the expansion and they won't get removed.

In the given case, you can leave them away, as your file names don't need any quoting, but you'll run into problems with file names with whitespaces or other special characters. And neither quotes nor backslashes will help you as they are not considered to be quotes at this stage of processing.

Edit to serve the "one step" request:

But what if you have a bunch of files with whitespace like foo file foo and bar file bar and want to cat some of them?

Your command line would read

cat "foo file foo" "bar file bar"

but if there is more, you'll want to script it.

echo -n cat; for name in foo bar; do echo -n \ \"$name file $name\"; done

Will produce exactly the line you would enter, but

$(echo -n cat; for name in foo bar; do echo -n \ \"$name file $name\"; done)

will fail for the reason explained above. Enclosing everything in double quotes (as did help in your gimp case) will not work either, because the new command needs to be separated in words, but not within the quotes.

So what to do to make quoting work as if entered directly in the command line? Pass your string as command to the shell:

bash -c "$(echo -n cat; for name in foo bar; do echo -n \ \"$name file $name\"; done)"

That should be the most general approach for a one-liner executing a generated command.

  • Yes, but...should there not be a way to do this in one step? – Michael Tiemann Jul 12 '17 at 15:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.