0

Context: Catalina = zsh (preferred) or 16.04 Ubuntu = bash

A qpdf example indicates:

# To merge (concatenate) all the pages of a list of PDF files and save the result as a new PDF:
qpdf --empty --pages <file1.pdf> <file2.pdf> <file3.pdf> -- <output.pdf>

A set of .pdf files (with spaces in filenames), in a particular directory, are to be concatenated:

# Concatenate Drafts file to ../concatDrafts.pdf   (76 pdf files)
# https://stackoverflow.com/a/53754681/4953146
qpdf --empty --pages *.pdf -- out.pdf

Although qpdf command is concatenates .pdf files, the reverse order of the .pdf files is to be concatenated. The order of the files to be processed is returned by:

ls -r.pdf

To handle spaces in the .pdf file names: xargs research indicates the need for:

ls -r *.pdf | xargs -E '\n'

What is the the thought process to arrive at a command the pipes the output of ls into the qpdf command?

gatorback
  • 1,384
  • 23
  • 48

3 Answers3

3

In zsh, it would just be:

qpdf --empty --pages ./*.pdf(On) -- output.pdf

Where On is a glob qualifier to order the glob expansion in reverse (capital O, lower case would be for straight) order by name.

You could also add the n glob qualifier for that by-name ordering to be numerical:

qpdf --empty --pages ./*.pdf(nOn) -- output.pdf

Compare:

$ print -r ./*.pdf(On)
./file3.pdf ./file2.pdf ./file1.pdf ./file11.pdf ./file10.pdf
$ (LC_ALL=C; print -r ./*.pdf(On))
./file3.pdf ./file2.pdf ./file11.pdf ./file10.pdf ./file1.pdf

(in lexical order, file10.pdf comes before file2.pdf, and even before file1.pdf in locales where punctuation (here .) is ignored in first approximation when comparing strings).

With:

$ print -r ./*.pdf(nOn)
./file11.pdf ./file10.pdf ./file3.pdf ./file2.pdf ./file1.pdf

The file10.pdf comes after file3.pdf because with n, sequences of decimal digits are compared numerically (that's similar to what is done by GNU ls -v or GNU sort -V).

  • Thank you for the elegant solution & concise response. I believe that the (On) glob qualifier reverses the order & solves the problem. I do not understand the (nOn) glob qualifier numerical ordering: please consider posting a link to a simple concrete example. – gatorback Nov 02 '20 at 16:27
  • @gatorback, see edit. – Stéphane Chazelas Nov 02 '20 at 16:37
  • The Chazelas solution is elegant (minimalism) and the example is very helpful to those learning the craft. – gatorback Nov 02 '20 at 20:09
  • Follow-on question regarding ./ posted: https://unix.stackexchange.com/questions/617789/what-purpose-does-dot-slash-serve – gatorback Nov 03 '20 at 13:13
1

(For a GNU bash shell)

You can use tac to reverse the list of the arguments going to xargs. For the qpdf command you run, we have to combine two more arguments: -- and out.pdf at the end.

Here using the newline as the arguments separator, that means filenames with newlines are not handled:

printf "%s\n" "out.pdf" "--" *.pdf | tac | xargs -d'\n' qpdf --empty --pages

And here for any filenames, using the null separator:

printf "%s\0" "out.pdf" "--" *.pdf | tac -s $'\0' | xargs -0 qpdf --empty --pages

I tested (GNU Bash shell on Linux), it concatenated the files in the expected reversed order.

thanasisp
  • 8,122
1

You can use answers to How to reverse shell arguments? to reverse what *.pdf expands to. First store this as positional parameters of the shell:

set -- *.pdf

Then use any good answer to the linked question. I choose this one:

flag=''; for a in "$@"; do set -- "$a" ${flag-"$@"}; unset flag; done

Now "$@" expands to what you want. Use it with your desired command:

qpdf --empty --pages "$@" -- out.pdf

If you don't want to lose old positional parameters then run the three commands in a subshell.