PPDF123
How-to2026-09-2910 хв читання

pdfx у терміналі та в CI: від першої команди до надійного скрипта

Використовуйте pdfx у командному рядку, щоб об’єднувати, стискати й додавати водяні знаки, обробляти багато файлів одразу та зчіплювати інструменти в один запит через конвеєр. Для скриптів і CI з’ясуйте, на що покладатися: коди виходу, звіт --json, стандартний ввід і вивід, а також чому умовний інструмент-фільтр без збігу все одно завершується з кодом 0.

PDF123 · Updated 2026-09-29

Перш ніж ставити pdfx у скрипт або в безперервну інтеграцію (CI), запам’ятайте дві речі. Код виходу 0 не завжди означає, що файл записано: умовний інструмент-фільтр без збігу теж завершується з кодом 0. А --json має різну форму для одного вхідного файлу і для кількох, тож скрипт, який розбирає лише масив files, нічого не знайде, коли збігся лише один файл. pdfx — це HTTP-клієнт: файли завантажуються на сервер для обробки, локального рушія немає, режиму офлайн теж. Повний довідник команд — у посібнику з CLI на сторінці для розробників.

Діаграма: один виклик pdfx віддає скриптові три речі: звіт JSON у стандартному виводі, повідомлення про помилки в стандартному потоці помилок і код виходу; скрипт спершу читає код виходу

Встановіть і об’єднайте два файли

Потрібен Node 20.3 або новіший, або Bun:

npm install -g @pdf123/cli
pdfx --version

Якщо не хочете встановлювати глобально, додайте перед командою npx @pdf123/cli. Маючи два PDF у поточній теці:

pdfx merge a.pdf b.pdf -o merged.pdf

У разі успіху стандартний вивід друкує merged.pdf. Це був інструмент Об’єднати PDF. Перш ніж перейти до іншого інструмента, запитайте його поля, а не вгадуйте назви параметрів:

pdfx describe watermark
Add Watermark (watermark) - Add text or image watermarks to PDF files
Input:    1 file (.pdf)
Result:   file
Fields:
  --watermarkText <value>  Watermark text [default: PDF123]
  --fontSize <number>      Font size [default: 30]
                           min 6, max 200

(Це уривок; серед полів є також --rotation, --customColor та інші.) Поле — це просто параметр командного рядка:

pdfx watermark in.pdf --watermarkText DRAFT -o marked.pdf

pdfx list виводить усі 95 інструментів, --category security показує лише одну категорію, а --query watermark шукає за словом.

Кілька файлів потрапляють у теку, наявні файли не перезаписуються

Дайте однофайловому інструменту кілька вхідних файлів, і кожен буде записано в теку, указану в -o, щойно він завершиться, а не буде утримуватися до кінця. Якщо один файл завершується помилкою, решта виконується далі, а код виходу наприкінці пакета дорівнює 1.

pdfx compress a.pdf b.pdf -o small/

Стандартний вивід одного запуску виглядав так, і порядок може щоразу відрізнятися:

a.pdf -> small/a.pdf
b.pdf -> small/b.pdf

Наявна тека працює як є; якщо теки ще не існує, після -o потрібна завершальна /, інакше small буде сприйнято як ім’я файлу. Без -o результати потрапляють у поточну теку під іменем, яке дає сервер; наявний файл не перезаписується, а новий стає a-1.pdf, a-2.pdf. Конкретне ім’я файлу (-o same.pdf) замінює наявний вміст, як ви й сказали. За замовчуванням одночасно обробляються два файли; змінити це можна через --concurrency. Якщо натиснути Ctrl-C посеред пакета, уже записані файли лишаються, а код виходу дорівнює 130.

Зашифрований файл відкривається через --input-password PASSWORD. Коли в пакеті змішано заблоковані й незаблоковані файли, скористайтеся --password-for FILE=PASSWORD, який можна повторювати.

Для проміжних файлів викликайте інструменти окремо, в інших випадках беріть конвеєр

Щоб об’єднати, потім додати водяний знак, потім стиснути, коли проміжні результати на диску не потрібні, згорніть усе в один запит через pipeline; проміжні файли не завантажуються й не вивантажуються знову. Якщо вам потрібен файл після кожного кроку, викликайте інструменти окремо. Конвеєр дає один файл.

pdfx pipeline a.pdf b.pdf --step merge --step compress -o merged-small.pdf

# Коли кроку потрібні параметри, опишіть кроки в JSON
pdfx pipeline a.pdf b.pdf \
  --steps '[{"tool":"merge"},{"tool":"watermark","params":{"watermarkText":"DRAFT"}},{"tool":"compress"}]' \
  -o out.pdf

Конвеєр має щонайбільше 8 кроків; дев’ятий крок отримує HTTP 400: at most 8 pipeline steps allowed. Інструмент, якому потрібен другий файл (наприклад, overlay-pdfs, що накладає інший PDF), не може бути кроком конвеєра. pdfx відхиляє його ще до завантаження, з кодом виходу 2 і повідомленням Tool "overlay-pdfs" needs a second file and cannot run as a pipeline step.

Стандартний ввід використовуйте лише тоді, коли хочете під’єднати команду до каналу. Аргумент-файл - читає зі стандартного вводу, а -o - записує результат у стандартний вивід:

cat report.pdf | pdfx compress - -o - > report-small.pdf

У цьому режимі стандартний вивід несе лише байти файлу; повідомлення й помилки йдуть у стандартний потік помилок, тож перенаправлення безпечне. Ми перевірили це на PDF близько 1 КБ: стандартний вивід містив PDF на 1040 байтів, такого самого розміру, як файл, записаний через -o, а стандартний потік помилок був порожній. Коли ви читаєте зі стандартного вводу й не вказуєте -o, результат отримує ім’я stdin.pdf, записується в поточну теку з приміткою в стандартному потоці помилок.

Скрипти мають зіставляти причину, а не повідомлення

Код виходу Значення Приклади
0 Успіх, або умовний інструмент-фільтр без збігу Об’єднання вдалося; умова filter-page-count була хибною
1 Запит надіслано, але він завершився помилкою; у пакеті принаймні один файл завершився помилкою Неправильний пароль, не PDF, тайм-аут, нічого повертати
2 Помилка використання, нічого не завантажено Помилка в назві інструмента, крок конвеєра, якому потрібен другий файл, тип результату суперечить розширенню вихідного файлу
130 Ви перервали виконання Ctrl-C під час пакета

У разі збою стандартний потік помилок, окрім рядка повідомлення, містить ще три рядки: reason:, code: і hint:. Зашифрований файл з неправильним паролем дав локально таке:

pdfx: HTTP 400: The password is incorrect.
reason: wrong_password
code: bad_request
hint: Check the password and try again.

Спосіб передачі пароля змінює перший рядок повідомлення: unlock --password дає рядок вище. З --input-password в іншому інструменті сервер вважає розблокування внутрішнім кроком, і перший рядок має вигляд HTTP 400: Pipeline step 0 (security/remove-password) failed: The password is incorrect., тоді як рядок reason: в обох випадках дорівнює wrong_password. Без пароля взагалі повідомлення таке: This PDF is password-protected. Enter its password., а reason — password_required. Скрипт має зіставляти рядок reason:. code грубіший, і поширене його значення — bad_request.

Помилка в назві інструмента завершується з кодом 2, а в повідомленні пропонуються схожі назви:

pdfx: Unknown tool "compres". Did you mean: compress, decompress-pdf? Run `pdfx list` to see all tools.

Тип результату, що суперечить імені файлу, теж завершується з кодом 2, причому до того, як щось буде записано. Запис ZIP, який створює Розрізати PDF, у x.pdf:

pdfx: The result is application/zip but "x.pdf" has a .pdf extension; name it *.zip or write into a directory
code: output_mismatch

Тайм-аут також завершується з кодом 1, а code дорівнює timeout. Одиниця --timeout — мілісекунди, типове значення 300000, тобто 5 хвилин, тож --timeout 60 означає 60 мілісекунд, а не 60 секунд.

Коли умова хибна, код виходу все одно 0

Один клас інструментів відповідає на питання «так чи ні»: чи кількість сторінок більша за N, чи містить файл певний текст, чи файл більший за певний розмір. Коли відповідь «так», вони повертають вхідний файл без змін; коли «ні», не повертають нічого, а pdfx друкує рядок no match і завершується з кодом 0. Ці інструменти-фільтри існують лише в SDK, командному рядку та MCP; на сайті сторінки для них немає.

Це відрізняється від іншого виду порожнього результату. Коли PDF у CSV не знаходить у PDF таблиці, сервер повертає 204, а pdfx завершується з кодом 1 і повідомляє no_content: інструмент мав створити вміст і не створив, а причину описано в матеріалі Порожній експорт таблиці (204): у вашому PDF, найімовірніше, немає колонок. Для інструмента-фільтра «немає збігу» — це відповідь, яку він і мав дати.

Щоб прочитати це в скрипті, використайте --json. Збіг друкує записаний файл; відсутність збігу друкує { "matched": false }:

# Збіг: файл записано, і виводяться відомості про нього
$ pdfx filter-page-count three.pdf --pageCount 2 --comparator Greater --json -o big/
{ "path": "big/three.pdf", "contentType": "application/pdf", "bytes": 2594 }

# Немає збігу: файл не записано
$ pdfx filter-page-count three.pdf --pageCount 5 --comparator Greater --json
{ "matched": false }

Щоб залишити лише файли з понад 2 сторінками, можна написати так. Ми запустили це локально на a.pdf (1 сторінка) і three.pdf (3 сторінки), і залишився лише другий:

mkdir -p big
for f in *.pdf; do
  if pdfx filter-page-count "$f" --pageCount 2 --comparator Greater --json -o big/ \
      | jq -e '.matched == false' >/dev/null; then
    echo "$f: пропущено"
  fi
done

jq -e '.matched == false' завершується з кодом 0, коли збігу немає, і з 1, коли збіг є. У разі збігу файл уже записано pdfx у big/; if лише вирішує, чи друкувати «пропущено», а не чи записувати.

Коли вхід один, у --json немає масиву files

Коли вхідних файлів кілька, стандартний вивід за --json є повним звітом. input — це абсолютний шлях. processed рахує файли, запит для яких вдався, зокрема й ті, що без збігу. unmatched — скільки з них не мали збігу, а їхні записи мають вигляд { "ok": true, "matched": false } без path. Коли жоден файл пакета не має збігу, код виходу все одно 0. Коли ви передаєте --idempotency-key для пакета, ключ, що надсилається для кожного файлу, має вигляд <key>:<index>; механізм описано в матеріалі Idempotency-Key: безпечні повторні спроби для завдань PDF.

{
  "processed": 2,
  "unmatched": 0,
  "failed": 1,
  "files": [
    { "input": "/work/a.pdf", "ok": true, "path": "out/a.pdf", "contentType": "application/pdf", "bytes": 1040 },
    { "input": "/work/broken.pdf", "ok": false, "error": "HTTP 400: The file is not a valid PDF or it is damaged.", "reason": "invalid_pdf" },
    { "input": "/work/b.pdf", "ok": true, "path": "out/b.pdf", "contentType": "application/pdf", "bytes": 1027 }
  ]
}

З єдиним вхідним файлом використовується шлях для одного файлу: --json друкує { "path": ..., "contentType": ..., "bytes": ... }, а масиву files немає. У разі збою стандартний вивід порожній, а все йде в стандартний потік помилок. Коли ви розгортаєте файли через glob, скрипт мусить обробляти обидва формати, збігся один файл чи кілька.

Зберіть правила вище в один крок CI

Цей скрипт лише стискає й не використовує інструмент-фільтр. Збій стискання завершується з кодом 1, і крок падає разом з ним. Інструмент-фільтр без збігу завершується з кодом 0, тож CI не падає лише через відсутність збігу; чи вважати відсутність збігу проблемою, вирішуєте ви, читаючи --json, як у розділі про фільтри вище.

Якщо будь-який файл відхилено, цей крок завершується помилкою й виводить у журнал імена файлів і причини. Логіка та сама, що й раніше: спершу читаємо код виходу, у разі збою друкуємо стандартний потік помилок, потім через jq дістаємо reason зі звіту пакета. Без аргумента-теки скрипт одразу виходить, щоб "$1"/*.pdf ніколи не розгорнувся в /*.pdf.

#!/usr/bin/env bash
# Використання: ./ci-step.sh docs
docs="${1:?використання: ./ci-step.sh <тека>}"
mkdir -p out
pdfx compress "$docs"/*.pdf -o out/ --json > report.json 2> errors.log
status=$?
if [ "$status" -ne 0 ]; then
  cat errors.log >&2
  jq -r '.files[]? | select(.ok | not) | "\(.input | split("/") | last)\t\(.reason)"' report.json >&2
fi
exit "$status"

Ми запустили його локально з чотирма видами вхідних даних:

Файли в теці Код виходу Журнал
Два справні PDF 0 немає
Два справні PDF і один пошкоджений 1 pdfx: broken.pdf: HTTP 400: ..., потім рядок broken.pdf invalid_pdf
Один справний PDF 0 немає; report.json у форматі для одного файлу
Один пошкоджений PDF 1 reason: invalid_pdf, code: invalid_document і рядок hint; report.json порожній

Знак питання наприкінці .files[]? у jq не дає звіту для одного файлу (де files немає) викликати помилку. Для єдиного пошкодженого файлу звіту пакета немає, а причина з’являється лише в errors.log, тож зберігайте обидва потоки виводу.

Ще три речі варто перевірити, перш ніж ставити це в CI. pdfx потребує мережі: файли завантажуються на PDFX_API_BASE, що за замовчуванням дорівнює https://pdf123.xyz. Для документів, які мають лишатися у вашій мережі, запустіть власний сервіс і спрямуйте цю змінну на нього; див. Що насправді дає власний хостинг (і чого це коштує). Коли потрібна ідентичність, кладіть ключ у PDFX_API_KEY, а не в аргументи командного рядка, де він залишився б у списку процесів і журналах. Наразі опублікована версія 0.1.0; у CI закріпіть її через npx @pdf123/[email protected] ..., щоб формат виводу й коди виходу не змінювалися з новими випусками, а оновлення стало зміною, яку ви робите свідомо.

Сторінка пакета на npm — @pdf123/cli.

Open tool
Process in the browser — no watermark, files removed after the job.
Open tool
Read next
Нумерація сторінок PDF: положення, початковий номер і пастка повернутих сторінок
Як пронумерувати сторінки наявного PDF онлайн: оберіть кут, задайте початковий номер, наприклад 101, і уникніть номерів набік, що з’являються на сторінках, збережених як повернуті.
Конвертація PDF у JPG або PNG: що змінює DPI і як експортувати одну сторінку
«PDF у зображення» рендерить кожну сторінку в ZIP, у PNG, якщо не вибрати JPEG. У формі в браузері є лише формат і DPI; поля API для вибору сторінок і WebP ігноруються. Виміряні розміри файлів при 150 і 300 DPI та як експортувати одну сторінку.
Фото з телефона в PDF: чому файл виходить величезним, а сторінка лягає набік
«Зображення в PDF» кладе кожне фото на окрему сторінку в повному піксельному розмірі, тож три JPEG по 3,2 МБ з телефона стали PDF на 41,5 МБ, а портретне фото потрапило на альбомну сторінку. Зменшіть фото перед конвертацією, а сторінку набік поверніть після неї.