21

I've got a double-side-printed multipage document to scan with a linear bulk scanner. So, as the result I get 2 PDF files: one containing all odd pages and the second containing all even pages. I need to merge them the natural way:

1. <- 1.1. (odd.pdf page 1 to result.pdf page 1)
2. <- 2.1. (even.pdf page 1 to result.pdf page 2)
3. <- 1.2. (odd.pdf page 2 to result.pdf page 3)
4. <- 2.2. (even.pdf page 2 to result.pdf page 4)

etc.

3
  • Just find a PDF parser and do a merge sort like stuff.
    – daisy
    Commented Oct 30, 2012 at 10:00
  • 1
    If Stephane doesn't solve your problem, you can try the perl module CAM::PDF, I'll give your a script later. Does the two pdf have same page count?
    – daisy
    Commented Oct 30, 2012 at 11:24
  • Same question on StackOverflow SuperUser
    – user202729
    Commented Nov 10, 2023 at 12:19

8 Answers 8

37

pdftk has a shuffle command which collates pages:

pdftk A=odd.pdf B=even.pdf shuffle A B output collated.pdf
1
  • 17
    This worked well for me, but with a tweak to reverse the even pages (given that I scanned them without first reversing the order of the pages): pdftk A=recto.pdf B=verso.pdf shuffle A Bend-1 output collated.pdf Commented Mar 1, 2019 at 16:06
11

See the pdfseparate and pdfunite commands from poppler-utils. The first to separate the pages from each document into individual files, and the second to merge them in the order you want in a new document.

Also note that since scanners give you raster images anyway (which some like yours can concatenate into a PDF files), maybe you can configure it to output images (png, tiff...) instead, and do the concatenation into a PDF yourself with ImageMagick.

4
  • This sounds like what I need, let's try...
    – Ivan
    Commented Oct 30, 2012 at 10:07
  • 1
    Indeed. Excellent. Simple to use to do right what I need. by the way, I have, of course, googled before asking and solutions I've found to exactly the same were notably more complex.
    – Ivan
    Commented Oct 30, 2012 at 13:07
  • 1
    I tried this on Ubuntu 18.04 (with the helpful script below from @TCF) and it turned two ~5.5Mb files into one 197Mb file, so while it did the job, it wasn't usable (I needed to email the result!). Commented Mar 1, 2019 at 16:03
  • Didn't know about pdfseparate, yippie. Separated pages with that, but combined them with pdftk on Ubuntu 22.04. Resulting file was of reasonable size. Commented Mar 27, 2023 at 13:00
2

Just a bash quick shot using pdfjam:

Build an array of input arguments:

for k in $(seq 1 ${N_PAGES}); do
    PAGES+=(odd.pdf);
    PAGES+=($k);
    PAGES+=(even.pdf);
    PAGES+=($k);
done

This should allow you to use it as input list for pdfjoin:

 pdfjoin ${PAGES[@]} --outfile shuffled.pdf
1
  • 2
    It should be noted that pdfjoin is a wrapper script around pdfjam which is itself a wrapper script around the pdfpages LaTeX package (and pdflatex) so it means that it brings LaTeX as a dependency. Commented Oct 30, 2012 at 14:11
2

I came up with this script which is based on the other answers.

In my case I scan the front pages and the back pages into the same file, so I first need to split the file in half and then shuffle it with pdftk:

#!/bin/bash

## Assume:

# We have
#
# 1 3 5 7 8 6 4 2 
# and want it in 
# 1 2 3 4 5 6 7 8

NO_OF_PAGES=`pdftk "$1" dump_data | grep NumberOfPages | cut -d " " -f 2`
HALF=`echo $(( $NO_OF_PAGES / 2 ))`


FRONTSIDES=1-$HALF

BACKSIDES=$(( $HALF + 1 ))-$NO_OF_PAGES


pdftk "$1" cat $FRONTSIDES output /tmp/$$fronts.pdf
pdftk "$1" cat $BACKSIDES output /tmp/$$backs.pdf

pdftk A=/tmp/$$fronts.pdf B=/tmp/$$backs.pdf shuffle A Bend-1 output "$1.remixed.pdf"
1

I came across this bash script doing this, it assumes you scanned the even pages in reverse order, but you can change this removing the -r in the line saying evenpages=($(ls "$evenbase-$key-"* | sort -r)) (this is line 46)

#!/bin/bash
# Copyright Fabien André <[email protected]>
# Distributed under the MIT license
# This script interleaves pages from two distinct PDF files and produces an
# output PDF file. The odd pages are taken from a first PDF file and the even
# pages are taken from a second PDF file passed respectively as first and second
# argument.
# The first two pages of the output file are the first page of the
# odd pages PDF file and the *last* page of the even pages PDF file. The two
# following pages are the second page of the odd pages PDF file and the
# second to last page of the even pages PDF file and so on.
#
# This is useful if you have two-sided documents scanned each side on a
# different file as it can happen when using a one-sided Automatic Document
# Feeder (ADF)
#
# It does a similar job to :
# https://github.com/weltonrodrigo/pdfapi2/blob/46434ab3f108902db2bc49bcf06f66544688f553/contrib/pdf-interleave.pl
# but only requires bash (> 4.0) and poppler utils.
# Print usage/help message
function usage {
echo "Usage: $0 <PDF-even-pages-file> <PDF-odd-pages-file>"
exit 1
}
# Add leading zeros to pad numbers in filenames matching the pattern
# $prefix$number.pdf. This allows filenames to be easily sorted using
# sort.
# $1 : The prefix of the filenames to consider
function add_leading_zero {
prefix=$1
baseprefix=$(basename $prefix | sed -e 's/[]\/()$*.^|[]/\\&/g')
dirprefix=$(dirname $prefix)
for filename in "$prefix"*".pdf"
do
base=$(basename "$filename")
index=$(echo "$base" | sed -rn "s/$baseprefix([0-9]+).pdf$/\1/p")
newbase=$(printf "$baseprefix%04d.pdf" $index)
mv $filename "$dirprefix/$newbase"
done
}
# Interleave pages from two distinct PDF files and produce an output PDF file.
# Note that the pages from the even pages file (second file) will be used in
# the reverse order (last page first).
# $1 : Odd pages filename
# $2 : Odd pages filename with extension removed
# $3 : Even pages filename
# $4 : Even pages filename with extension removed
# $5 : Unique key used for temporary files
# $6 : Output file
function pdfinterleave {
oddfile=$1
oddbase=$2
evenfile=$3
evenbase=$4
key=$5
outfile=$6
# Odd pages
pdfseparate $oddfile "$oddbase-$key-%d.pdf"
add_leading_zero "$oddbase-$key-"
oddpages=($(ls "$oddbase-$key-"* | sort))
# Even pages
pdfseparate $evenfile "$evenbase-$key-%d.pdf"
add_leading_zero "$evenbase-$key-"
evenpages=($(ls "$evenbase-$key-"* | sort -r))
# Interleave pages
pages=()
for((i=0;i<${#oddpages[@]};i++))
do
pages+=(${oddpages[i]})
pages+=(${evenpages[i]})
done
pdfunite ${pages[@]} "$outfile"
rm ${oddpages[@]}
rm ${evenpages[@]}
}
if [ $# -lt 2 ]
then
usage
fi
if [ $1 == $2 ]
then
echo "Odd pages file and even pages file must be different." >&2
exit 1
fi
if ! hash pdfunite 2>/dev/null || ! hash pdfseparate 2>/dev/null
then
echo "This script requires pdfunite and pdfseparate from poppler utils" \
"to be in the PATH. On Debian based systems, they are found in the" \
"poppler-utils package"
exit 1
fi
oddbase=${1%.*}
evenbase=${2%.*}
odddir=$(dirname $oddbase)
oddfile=$(basename $oddbase)
evenfile=$(basename $evenbase)
outfile="$odddir/$oddfile-$evenfile-interleaved.pdf"
key=$(tr -dc "[:alpha:]" < /dev/urandom | head -c 8)
if [ -e $outfile ]
then
echo "Output file $outfile already exists" >&2
exit 1
fi
pdfinterleave $1 $oddbase $2 $evenbase $key $outfile
# SO - Bash command that prints a message on stderr
# http://stackoverflow.com/questions/2643165/bash-command-that-prints-a-message-on-stderr
# SO - Check if a program exists from a bash script
# http://stackoverflow.com/questions/592620/check-if-a-program-exists-from-a-bash-script
# SO - How to debug a bash script?
# http://stackoverflow.com/questions/951336/how-to-debug-a-bash-script
# SO - Escape a string for sed search pattern
# http://stackoverflow.com/questions/407523/escape-a-string-for-sed-search-pattern

Source

1

You can use the Mix module in PDFsam Basic (free and open source) or do it online using the Alternate & Mix feature in Sejda

1
  • 1
    Hands down the best solution: open source, no installation required. 2 clicks and done ;)
    – nathan
    Commented Dec 24, 2019 at 20:33
0

I was looking to do basically the same thing, and Stéphane Chazelas's answer was very helpful. I do this often enough that I wrote a simple Python script to automate things, using the commands he suggested. By default, it reverses the order of the even pages, but this can be suppressed with a command line flag.

The question is kind of old, so I expect the needs of the original asker have already been met. However, it is possible that the script will be useful to people who arrive here in the future, so I have placed it below.

#!/usr/bin/python
"""A simple script to merge two PDFs."""

import argparse
from os import listdir
from os.path import join as opjoin
import shutil
from subprocess import check_call, CalledProcessError
import tempfile

SEPARATE = 'pdfseparate %s %s'
MERGE = 'pdfunite %s %s'

def my_exec(command):
    """Execute a command from a shell, ignoring errors."""
    try:
        check_call(command, shell=True)
    except CalledProcessError:
        pass

def run(odd, even, out, reverse_odd=False, reverse_even=True):
    """Interleave odd and even pages from two PDF files."""
    folder = tempfile.mkdtemp()
    my_exec(SEPARATE % (odd, opjoin(folder, 'odd%d.pdf')))
    my_exec(SEPARATE % (even, opjoin(folder, 'even%d.pdf')))
    odd_files = []
    even_files = []
    for curr_file in listdir(folder):
        filepath = opjoin(folder, curr_file)
        if curr_file.startswith('odd'):
            odd_files.append((filepath, int(curr_file[3:-4])))
        elif curr_file.startswith('even'):
            even_files.append((filepath, int(curr_file[4:-4])))
    func = lambda x: x[1]
    odd_files.sort(key=func, reverse=reverse_odd)
    even_files.sort(key=func, reverse=reverse_even)
    parts = []
    for line in zip(odd_files, even_files):
        parts.append(line[0][0])
        parts.append(line[1][0])
    my_exec(MERGE % (' '.join(parts), out))
    shutil.rmtree(folder)

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Merge two PDF files.')
    parser.add_argument('odd_pages', help='PDF containing the odd pages.')
    parser.add_argument('even_pages', help='PDF containing the even pages.')
    parser.add_argument('output_file', help='The target output file.')
    parser.add_argument('--reverse-odd', action='store_true', 
                        help='Insert the odd pages in reverse order.')
    parser.add_argument('--no-reverse-even', action='store_true',
                        help='Suppress reversal of the even pages.')
    args = parser.parse_args()
    run(args.odd_pages, args.even_pages, args.output_file,
        args.reverse_odd, not args.no_reverse_even)
0

Here is another possible solution, using qpdf.

qpdf --collate --empty --pages odd.pdf 1-z even.pdf z-1 -- merged.pdf

This assumes that you've fed the sheets of paper into your scanner in the same order, so the page order is reversed in the even pages document (i.e. the first page of even.pdf is in fact the back of the last sheet).

If you don't need reverse order, just replace z-1 after even.pdf with 1-z.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .