2

I want to join some png files into a pdf. The command

convert *png out.pdf

works, but gets the ordering wrong. It turns out that

ls -v *png

gives the ordering that I want, therefore I want to pass its output on to convert.

I have tried

convert `ls -v *png` out.pdf

but this doesn't work since the filenames contain spaces and parentheses.

Telling ls to set the filenames between double quotes via

convert `ls -vQ *png` out.pdf

doesn't work either since the quotes seem to get removed in the substitution.

What can I do?

12
  • 1
    Don't parse ls...
    – jasonwryan
    Sep 22, 2016 at 22:14
  • 2
    If you have a GNU based system, perhaps something like find . -maxdepth 1 -name '*.png' -print0 | sort -zV | xargs -0 will give the desired ordering? Sep 22, 2016 at 22:23
  • Or rename them (even links to them in a separate directory) with sequencing numbers as prefixes?
    – Jeff Schaller
    Sep 22, 2016 at 22:43
  • Thanks steeldriver, that seems to go in the right direction but how can I tell xargs to call the command once with all arguments instead of once for each?
    – user313032
    Sep 22, 2016 at 22:44
  • 2
    @user313032, Yeah... using eval can work here, but it's... a bit like pointing around with a gun. I deleted that answer to work on a less dangerous solution.
    – ilkkachu
    Sep 22, 2016 at 23:12

4 Answers 4

2

If you have a recent GNU-based system (whose sort provides a similar -V natural-version sort) you could sort using that, and read the result into an array:

while read -rd '' f; do 
  files+=("$f")
done < <(find . -maxdepth 1 -name '*png' -print0 | sort -zV)

Then you should be able to expand the array within your convert command:

convert "${files[@]}" out.pdf
2
  • Note that bash 4.4 supports readarray -td '' Sep 23, 2016 at 15:47
  • Thanks @StéphaneChazelas - I was searching for exactly that last night, but I only have bash 4..3 currently. I was also hoping you'd show up with a nice zsh solution ;) Sep 23, 2016 at 15:53
1

If your file names don't have any newlines in them, you might be able to do it with just telling the shell to split the output of ls only on newlines, not any whitespace as the default. The splitting is controlled by the IFS variable, any characters contained in IFS are used as delimiters.

IFS=$'\n'      # set it to just a newline
convert $(ls -vd ./*.png) output.pdf 

This may still have problems if the file names are funny enough, or if ls mangles them for display. When printing to a terminal, ls usually lists the files in multiple columns. But when the output doesn't go to a terminal (the shell reads it, here), it acts as if -1 was given.


To work with the idea you started with, you could use eval.

eval: eval [arg ...]
Combine ARGs into a single string, use the result as input to the shell, and execute the resulting commands.

But the problem with eval is that anything and everything in the command line gets parsed again, and even stuff that's usually safe, isn't. Think of a file called $(touch HELLO) and what happens if a name like that is dropped on the command line.

Also, if you go this way, you may want to use --quoting-style=shell instead of -Q as it may match the shell's processing of the special characters more closely. (Both may be specific to GNU ls, but I think -v is, too.)

5
  • Both solutions work, but I don't understand what the thing with IFS does. Does it mean that the output of the ls will be broken apart along newlines? This wouldn't work since we call ls without the -1 option, no?
    – user313032
    Sep 22, 2016 at 23:19
  • @user313032, edited, clarified. Basically the -1 is implied whenever the output of ls goes anywhere but a terminal. (It's as if they tried to make it work with scripts even though some characters in file names are still a problem.) You can try something like ls | cat to see the difference.
    – ilkkachu
    Sep 22, 2016 at 23:28
  • Thanks I see. How can I see the value of IFS? The command echo $IFS only gives a newline.
    – user313032
    Sep 22, 2016 at 23:40
  • @user313032, and always will, because without quotes, its contents are split to words... along the characters of IFS itself. Since it's going to have special characters in it, it may be best to use something like echo "$IFS" | od -c to get them escaped in the C-style.
    – ilkkachu
    Sep 22, 2016 at 23:47
  • You'd also need to disable globbing for your split+glob operator: IFS=$'\n'; set -f; mv $(set +f; ls -vd ./*.png) out.pdf Sep 23, 2016 at 15:49
1

With zsh, the (n) glob qualifier gives you a similar sorting order as GNU ls -v:

convert ./*.png(n) out.pdf

On a GNU system, with bash, zsh, yash, mksh or ksh93:

eval "files=($(ls --quoting-style=shell-always -vd ./*.png))"
convert "${files[@]}" out.pdf
0

Using GNU Parallel it looks like this:

ls -vd ./*png | parallel -Xj1 convert {} out.pdf

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.