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?

wjandrea
  • 658
  • 1
  • 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? – steeldriver Sep 22 '16 at 22:23
  • Or rename them (even links to them in a separate directory) with sequencing numbers as prefixes? – Jeff Schaller Sep 22 '16 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 '16 at 22:44
  • To be precise, I use find . -maxdepth 1 -name '*png' -print0 | sort -zV | xargs -0 -I files -t convert files out.pdf but that calls convert once for each filename. – user313032 Sep 22 '16 at 22:53
  • ilkkachu posted an answer that worked, but when i wanted to accept it it had disappeared. this is the thing that worked for me: eval convert $(ls -Qv *png) out.pdf. I know that I could also rename the files, but the format with spaces and parentheses is how they are exported by an ipad app, and I just want to transform them into something useful with minimal effort. So ilkkachu if you post your answer again, or whoever deleted it undeletes it, I will accept it. – user313032 Sep 22 '16 at 23:09
  • @user313032, you can't, not with GNU xargs, it insists on one file per command when using -I. BSD find has -J which is similar, but doesn't have that limitation. – ilkkachu Sep 22 '16 at 23:09
  • 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 '16 at 23:12
  • This doesn't make sense. Glob expansion sorting should be the same as ls sorting. Unless you have directories involved, but your png extension seems to indicate that you don't. I question your conclusions and request that you show us the "wrong order" you are getting. – Wildcard Sep 23 '16 at 00:37
  • The -v option changes the sorting. – user313032 Sep 23 '16 at 00:38
  • @user313032, aha, I was checking a BSD man page for the -v option of ls; silly me. There are other ways to get the files sorted the way you want, though. I would not use eval for this use case no matter what. – Wildcard Sep 23 '16 at 00:42
  • Yes I can see now why eval is very bad style. Thanks! – user313032 Sep 23 '16 at 00:56

4 Answers4

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
steeldriver
  • 81,074
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.)

ilkkachu
  • 138,973
  • 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 '16 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 '16 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 '16 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 '16 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 – Stéphane Chazelas Sep 23 '16 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
Ole Tange
  • 35,514