Usar pdfx al terminal i a la CI: de la primera ordre a un script fiable
Useu pdfx a la línia d'ordres per unir, comprimir i afegir marques d'aigua, processar molts fitxers alhora i encadenar eines en una sola petició amb un pipeline. Per a scripts i CI, apreneu en què podeu confiar: els codis de sortida, l'informe de --json, l'entrada i la sortida estàndard, i per què una eina de filtre condicional sense coincidència continua sortint amb 0.

Abans de posar pdfx en un script o en la integració contínua (CI), recordeu dues coses. El codi de sortida 0 no sempre vol dir que s'hagi escrit un fitxer: una eina de filtre condicional sense coincidència també surt amb 0. I --json té una forma diferent per a una entrada que per a diverses, de manera que un script que només analitza la matriu files no troba res quan només ha coincidit un fitxer. pdfx és un client HTTP: els fitxers es pugen al servidor per processar-los, no hi ha motor local i no hi ha mode fora de línia. La referència completa de les ordres és a la guia de la CLI a la pàgina per a desenvolupadors.
Instal·leu-lo i uniu dos fitxers
Cal Node 20.3 o posterior, o Bun:
npm install -g @pdf123/cli
pdfx --version
Si preferiu no instal·lar-lo globalment, poseu npx @pdf123/cli davant de l'ordre. Amb dos PDF al directori actual:
pdfx merge a.pdf b.pdf -o merged.pdf
Si va bé, la sortida estàndard imprimeix merged.pdf. Això era Uneix PDF. Abans de canviar d'eina, demaneu els camps en lloc d'endevinar noms de paràmetres:
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
(Un fragment; els camps també inclouen --rotation, --customColor i d'altres.) Un camp és simplement una opció de la línia d'ordres:
pdfx watermark in.pdf --watermarkText DRAFT -o marked.pdf
pdfx list llista les 95 eines, --category security en llista només una categoria, i --query watermark cerca per paraula.
Diversos fitxers van a un directori i els fitxers existents no se sobreescriuen
Doneu a una eina d'un sol fitxer diverses entrades i cada fitxer s'escriu al directori indicat per -o tan bon punt acaba, sense esperar al final. Si un fitxer falla, els altres continuen, i el codi de sortida al final del lot és 1.
pdfx compress a.pdf b.pdf -o small/
La sortida estàndard d'una execució va ser així, i l'ordre pot variar cada vegada:
a.pdf -> small/a.pdf
b.pdf -> small/b.pdf
Un directori existent funciona tal qual; si el directori encara no existeix, -o necessita una / al final; si no, small es tracta com un nom de fitxer. Sense -o, els resultats van al directori actual amb el nom que dona el servidor; un fitxer existent no se sobreescriu i el nou passa a ser a-1.pdf, a-2.pdf. Un nom de fitxer concret (-o same.pdf) substitueix el contingut existent, tal com heu demanat. Per defecte es processen dos fitxers alhora; es canvia amb --concurrency. Si premeu Ctrl-C a mig lot, els fitxers ja escrits es mantenen i el codi de sortida és 130.
Obriu un fitxer xifrat amb --input-password PASSWORD. Quan un lot barreja fitxers bloquejats i desbloquejats, useu --password-for FILE=PASSWORD, que es pot repetir.
Crideu les eines per separat per tenir fitxers intermedis; si no, useu un pipeline
Per unir, després afegir una marca d'aigua i després comprimir, quan no necessiteu els resultats intermedis al disc, plegueu-ho tot en una sola petició amb pipeline; els fitxers intermedis no es baixen ni es tornen a pujar. Si voleu el fitxer de cada pas, crideu les eines per separat. Un pipeline produeix un sol fitxer.
pdfx pipeline a.pdf b.pdf --step merge --step compress -o merged-small.pdf
# When a step needs parameters, describe the steps in JSON
pdfx pipeline a.pdf b.pdf \
--steps '[{"tool":"merge"},{"tool":"watermark","params":{"watermarkText":"DRAFT"}},{"tool":"compress"}]' \
-o out.pdf
Un pipeline té com a màxim 8 passos; un novè pas rep HTTP 400: at most 8 pipeline steps allowed. Una eina que necessita un segon fitxer (per exemple overlay-pdfs, que superposa un altre PDF) no pot ser un pas d'un pipeline. pdfx la rebutja abans de pujar res, amb codi de sortida 2 i el missatge Tool "overlay-pdfs" needs a second file and cannot run as a pipeline step.
Useu l'entrada estàndard només quan vulgueu connectar l'ordre a una canonada. Un argument de fitxer - llegeix de l'entrada estàndard, i -o - escriu el resultat a la sortida estàndard:
cat report.pdf | pdfx compress - -o - > report-small.pdf
En aquest mode la sortida estàndard només porta els bytes del fitxer; els missatges i els errors van a la sortida d'errors estàndard, de manera que redirigir és segur. Ho hem comprovat amb un PDF d'aproximadament 1 KB: la sortida estàndard contenia un PDF de 1040 bytes, de la mateixa mida que el fitxer escrit amb -o, i la sortida d'errors estàndard era buida. Quan llegiu de l'entrada estàndard i no doneu -o, el resultat es diu stdin.pdf, s'escriu al directori actual i s'imprimeix una nota a la sortida d'errors estàndard.
Els scripts han de comparar el motiu, no el missatge
| Codi de sortida | Significat | Exemples |
|---|---|---|
| 0 | Èxit, o una eina de filtre condicional sense coincidència | Una unió ha anat bé; la condició de filter-page-count era falsa |
| 1 | La petició s'ha enviat però ha fallat; en un lot, ha fallat almenys un fitxer | Contrasenya incorrecta, no és un PDF, temps d'espera, res per retornar |
| 2 | Error d'ús, no s'ha pujat res | Nom d'eina mal escrit, un pas de pipeline que necessita un segon fitxer, un tipus de resultat que contradiu l'extensió del fitxer de sortida |
| 130 | L'heu interromput vosaltres | Ctrl-C durant un lot |
Quan falla, a més d'una línia de missatge, la sortida d'errors estàndard porta tres línies: reason:, code: i hint:. Un fitxer xifrat amb la contrasenya incorrecta va produir això en local:
pdfx: HTTP 400: The password is incorrect.
reason: wrong_password
code: bad_request
hint: Check the password and try again.
La manera de passar la contrasenya canvia la primera línia del missatge: unlock --password dona la línia de dalt. Amb --input-password en una altra eina, el servidor tracta el desbloqueig com un pas intern i la primera línia és HTTP 400: Pipeline step 0 (security/remove-password) failed: The password is incorrect., mentre que la línia reason: és wrong_password en tots dos casos. Sense cap contrasenya, el missatge és This PDF is password-protected. Enter its password. i reason és password_required. Un script ha de comparar la línia reason:. code és més gruixut, i un valor habitual és bad_request.
Un nom d'eina mal escrit surt amb 2, i el missatge suggereix noms semblants:
pdfx: Unknown tool "compres". Did you mean: compress, decompress-pdf? Run `pdfx list` to see all tools.
Un tipus de resultat que contradiu el nom del fitxer també surt amb 2, i passa abans que s'escrigui res. Escriure a x.pdf el ZIP que produeix Parteix 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
Un temps d'espera esgotat també surt amb 1, amb code igual a timeout. La unitat de --timeout són els mil·lisegons i el valor per defecte és 300000, és a dir, 5 minuts, de manera que --timeout 60 vol dir 60 mil·lisegons, no 60 segons.
Quan la condició és falsa, el codi de sortida continua sent 0
Una classe d'eines respon una pregunta de sí o no: el nombre de pàgines és més gran que N, el fitxer conté un text determinat, el fitxer pesa més que una mida. Quan la resposta és sí, retornen el fitxer d'entrada sense canvis; quan és no, no retornen res, i pdfx imprimeix una línia no match i surt amb 0. Aquestes eines de filtre només existeixen a l'SDK, a la línia d'ordres i a MCP; el lloc web no té cap pàgina per a elles.
Això és diferent d'un altre tipus de resultat buit. Quan PDF a CSV no troba cap taula al PDF, el servidor retorna 204 i pdfx surt amb 1 i informa no_content: l'eina havia de produir contingut i no ho ha fet, i el motiu és a Exportació de taula buida (204): el teu PDF probablement no té columnes. Per a una eina de filtre, «cap coincidència» és la resposta que havia de donar.
Per llegir-ho en un script, useu --json. Una coincidència imprimeix el fitxer escrit; sense coincidència s'imprimeix { "matched": false }:
# Match: the file is written, and its info is printed
$ pdfx filter-page-count three.pdf --pageCount 2 --comparator Greater --json -o big/
{ "path": "big/three.pdf", "contentType": "application/pdf", "bytes": 2594 }
# No match: no file is written
$ pdfx filter-page-count three.pdf --pageCount 5 --comparator Greater --json
{ "matched": false }
Per quedar-vos només amb els fitxers de més de 2 pàgines, ho podeu escriure així. L'hem executat en local amb a.pdf (1 pàgina) i three.pdf (3 pàgines), i només es va conservar el segon:
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: omès"
fi
done
jq -e '.matched == false' surt amb 0 quan no hi ha coincidència i amb 1 quan n'hi ha. Quan hi ha coincidència, pdfx ja ha escrit el fitxer a big/; l'if només decideix si s'imprimeix «omès», no si s'escriu.
Amb una sola entrada, --json no té matriu files
Amb diverses entrades, la sortida estàndard amb --json és un informe complet. input és una ruta absoluta. processed compta els fitxers la petició dels quals ha reeixit, inclosos els que no han coincidit. unmatched és quants d'aquests no han coincidit, i les seves entrades són { "ok": true, "matched": false } sense path. Quan cap fitxer d'un lot coincideix, el codi de sortida continua sent 0. Quan passeu --idempotency-key a un lot, la clau que s'envia per a cada fitxer és <key>:<index>; el mecanisme és a Idempotency-Key: reintents segurs per a tasques 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 }
]
}
Amb una sola entrada s'usa el camí d'un sol fitxer: --json imprimeix { "path": ..., "contentType": ..., "bytes": ... } i no hi ha matriu files. Quan falla, la sortida estàndard és buida i tot va a la sortida d'errors estàndard. Quan expandiu fitxers amb un glob, l'script ha de tractar tots dos formats, tant si ha coincidit un fitxer com si n'han coincidit diversos.
Reuniu les regles anteriors en un sol pas de CI
Aquest script només comprimeix i no fa servir cap eina de filtre. Un error de compressió surt amb 1, i el pas falla amb ell. Una eina de filtre sense coincidència surt amb 0, de manera que la CI no falla només perquè no hi hagi coincidència; si la manca de coincidència és un problema o no, ho decidiu vosaltres llegint --json, com a la secció dels filtres d'abans.
Si es rebutja algun fitxer, aquest pas falla i llista al registre els noms dels fitxers i els motius. La lògica és la mateixa d'abans: llegiu primer el codi de sortida, imprimiu la sortida d'errors estàndard si falla, i després useu jq per treure el reason de l'informe del lot. Sense argument de directori surt de seguida, perquè "$1"/*.pdf no s'expandeixi mai a /*.pdf.
#!/usr/bin/env bash
# Ús: ./ci-step.sh docs
docs="${1:?ús: ./ci-step.sh <directori>}"
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"
L'hem executat en local amb quatre tipus d'entrada:
| Fitxers al directori | Codi de sortida | Registre |
|---|---|---|
| Dos PDF bons | 0 | cap |
| Dos PDF bons i un de malmès | 1 | pdfx: broken.pdf: HTTP 400: ..., i després una línia broken.pdf invalid_pdf |
| Un sol PDF bo | 0 | cap; report.json és en el format d'un sol fitxer |
| Un sol PDF malmès | 1 | reason: invalid_pdf, code: invalid_document i una línia hint; report.json és buit |
El signe d'interrogació al final de .files[]? a jq evita que l'informe d'un sol fitxer (que no té files) provoqui un error. Un sol fitxer malmès no té informe de lot i el motiu només apareix a errors.log, així que conserveu les dues línies de sortida.
Cal comprovar tres coses més abans de posar això a la CI. pdfx necessita xarxa: els fitxers es pugen a PDFX_API_BASE, que per defecte és https://pdf123.xyz. Per als documents que han de quedar dins la vostra xarxa, executeu un servei propi i apunteu aquesta variable cap a ell; vegeu Què us aporta realment l'autoallotjament (i quant costa). Quan necessiteu una identitat, poseu la clau a PDFX_API_KEY i no als arguments de la línia d'ordres, on es quedaria a la llista de processos i als registres. La versió publicada actualment és la 0.1.0; a la CI useu npx @pdf123/[email protected] ... per fixar-la, de manera que el format de sortida i els codis de sortida no canviïn amb les noves versions i una actualització sigui un canvi que feu a propòsit.
La pàgina del paquet a npm és @pdf123/cli.