Το pdfx στο τερματικό και στο CI: από την πρώτη εντολή σε ένα αξιόπιστο σενάριο
Χρησιμοποιήστε το pdfx στη γραμμή εντολών για να συγχωνεύσετε, να συμπιέσετε και να βάλετε υδατογράφημα, να επεξεργαστείτε πολλά αρχεία μαζί και να αλυσοδέσετε εργαλεία σε ένα αίτημα με αγωγό. Για σενάρια και CI μάθετε σε τι μπορείτε να βασιστείτε: στους κωδικούς εξόδου, στην αναφορά του --json, στην τυπική είσοδο και έξοδο, και γιατί ένα εργαλείο υπό συνθήκη φιλτραρίσματος χωρίς αντιστοίχιση βγαίνει πάλι με 0.

Πριν βάλετε το pdfx σε σενάριο ή σε συνεχή ενσωμάτωση (CI), θυμηθείτε δύο πράγματα. Ο κωδικός εξόδου 0 δεν σημαίνει πάντα ότι γράφτηκε αρχείο: ένα εργαλείο υπό συνθήκη φιλτραρίσματος χωρίς αντιστοίχιση βγαίνει επίσης με 0. Και το --json έχει διαφορετική μορφή για μία είσοδο και για πολλές, οπότε ένα σενάριο που αναλύει μόνο τον πίνακα files δεν βρίσκει τίποτα όταν ταίριαξε μόνο ένα αρχείο. Το pdfx είναι πελάτης HTTP: τα αρχεία ανεβαίνουν στον διακομιστή για επεξεργασία, δεν υπάρχει τοπική μηχανή και δεν υπάρχει λειτουργία εκτός σύνδεσης. Η πλήρης αναφορά εντολών βρίσκεται στον οδηγό CLI στη σελίδα για προγραμματιστές.
Εγκαταστήστε το και μετά συγχωνεύστε δύο αρχεία
Χρειάζεστε 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-for ΑΡΧΕΙΟ=ΚΩΔΙΚΟΣ, που μπορείτε να επαναλάβετε.
Καλέστε τα εργαλεία χωριστά για ενδιάμεσα αρχεία, αλλιώς χρησιμοποιήστε αγωγό
Για να συγχωνεύσετε, μετά να βάλετε υδατογράφημα και μετά να συμπιέσετε, όταν δεν χρειάζεστε τα ενδιάμεσα αποτελέσματα στον δίσκο, συγκεντρώστε τα σε ένα αίτημα με το pipeline· τα ενδιάμεσα αρχεία δεν κατεβαίνουν και δεν ξαναανεβαίνουν. Αν θέλετε το αρχείο κάθε βήματος, καλέστε τα εργαλεία χωριστά. Ένας αγωγός παράγει ένα μόνο αρχείο.
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
Ένας αγωγός έχει το πολύ 8 βήματα· ένα 9ο βήμα παίρνει 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
Σε αυτή τη λειτουργία η τυπική έξοδος μεταφέρει μόνο τα bytes του αρχείου· τα μηνύματα και τα σφάλματα πάνε στο τυπικό σφάλμα, άρα η ανακατεύθυνση είναι ασφαλής. Το ελέγξαμε με ένα PDF περίπου 1 KB: η τυπική έξοδος περιείχε ένα PDF 1040 bytes, ίδιου μεγέθους με το αρχείο που γράφτηκε με -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, και συμβαίνει πριν γραφτεί οτιδήποτε. Εδώ, το γράψιμο στο x.pdf του ZIP που παράγει ο Διαχωρισμός 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 }:
# 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 }
Για να κρατήσετε μόνο τα αρχεία με περισσότερες από 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.