I'm trying to put a bunch of images together into a pdf. I ran gm convert *.jpg out.pdf and it worked, but the images were not in the right order.
I found that ls -v orders them correctly so then I tried gm convert `ls -v *.jpg` out.pdf, but that failed because the files have spaces in the names.

So I checked the ls options and tried gm convert `ls -Qv *.jpg` out.pdf and gm convert `ls -bv *.jpg` out.pdf but both failed because, as far as I can tell, the file names got escaped twice (using \" in the first case, and \\ before space in the 2nd case).
Why is this happening, and how can I get the file names escaped correctly, i.e. only once? I'm using zsh if that's relevant.


You can't use escaping this way. You can use quotes or backslashes for something that understands them like the shell parsing.

There's nothing to escape the word splitting done upon variable expansion or command substitution (which is different from the shell parsing (tokenising) of a command line).

What you can do is either change the internal field delimiter $IFS to split on newline characters only (but note that except with zsh, you also need to disable filename generation (though not for the expansion of *.jpg obviously) which is the other thing done upon unquoted command substitution, unless you can guarantee that no filename contains globbing characters):

set -f # disable filename generation
' # set IFS  to newline character
IFS=$'\n' # alternative for ksh/zsh/bash
gm convert $(set +f; ls -v ./*.jpg) out.pdf # you also need ./ in case file
                                            # names start with -

(that assumes none of the file names contain newline characters)

set -f in zsh (unless in sh/ksh emulation) disables reading rc files (think zsh -f, csh -f), which has no effect once the shell is already started so is harmless.

With zsh, you can shorten that to:

gm convert ${(f)"$(ls -v ./*.jpg)"} out.pdf

(f) short for (ps:\n:) is to split on newline characters.

Alternatively, you could use ls quoting to build a shell command line (shell code in shell syntax if you like) to pass to eval which would let the shell evaluate it and run it:

With ksh93, zsh or bash:

eval "images=($(ls --quoting-style=shell -v ./*.jpg))"
gm convert "${images[@]}" out.pdf

But with zsh, you could simply do:

gm convert ./*.jpg(n) out.pdf

(n) is a globbing qualifier that tells the shell to sort the generated file names numerically.

| improve this answer | |
  • Thanks a lot for the detailed answer. The (n) feature is awesome, too bad it gets so complicated in the general case. I guess generating the whole command would be easier to get my head around, something like echo gm convert `ls -Qv *.jpg` out.pdf and either redirect it into a file or copy and paste the output. – aditsu Mar 13 '13 at 7:30
  • ...or simply pipe it to sh :) – aditsu Mar 13 '13 at 7:36
  • @aditsu, that's what eval's for. But that wouldn't work like that. Above you're still splitting the output of ls and echo (like eval would do as well) concatenates them with one space (instead of what caused the splitting in the first place). Also -Q is not the write quoting style for the shell (see for instance files with $ or ` or newline in their name) and echo expands backslash sequences. – Stéphane Chazelas Mar 13 '13 at 7:39
  • Hmm it happens to work in my case, but I guess it's not a general solution. I find the eval solution confusing because of the "${images[@]}" part. – aditsu Mar 13 '13 at 7:45
  • How does set -f work? I tried set -f;echo *.jpg and it still listed all the files. Or do I need to use it in a script? – aditsu Mar 13 '13 at 8:00

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.