टर्मिनल और CI में pdfx का इस्तेमाल: पहली कमांड से भरोसेमंद स्क्रिप्ट तक
कमांड लाइन पर pdfx से PDF मिलाएँ, संपीड़ित करें और वॉटरमार्क लगाएँ, एक साथ कई फ़ाइलें प्रोसेस करें और पाइपलाइन से उपकरणों को एक अनुरोध में जोड़ें। स्क्रिप्ट और 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 से खोलें। जब बैच में लॉक और बिना लॉक वाली फ़ाइलें मिली हों, तो --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 चरण होते हैं; 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
इस मोड में मानक आउटपुट पर केवल फ़ाइल के बाइट होते हैं; संदेश और त्रुटियाँ मानक त्रुटि पर जाती हैं, इसलिए रीडायरेक्ट करना सुरक्षित है। हमने लगभग 1 KB की PDF से इसे जाँचा: मानक आउटपुट में 1040 बाइट की PDF थी, जो -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 देता है, और यह कुछ भी लिखे जाने से पहले होता है। PDF बाँटें जो ZIP बनाता है उसे 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 सरणी नहीं होती। विफलता पर मानक आउटपुट ख़ाली रहता है और सब कुछ मानक त्रुटि पर होता है। जब आप ग्लोब से फ़ाइलें फैलाते हैं, तो स्क्रिप्ट को दोनों रूप सँभालने होंगे, चाहे एक फ़ाइल मेल खाए या कई।
ऊपर के नियमों को एक CI चरण में समेटें
यह स्क्रिप्ट केवल संपीड़ित करती है और कोई फ़िल्टर उपकरण इस्तेमाल नहीं करती। संपीड़न की विफलता 1 के साथ समाप्त होती है, और चरण उसी के साथ विफल हो जाता है। मेल न खाने वाला फ़िल्टर उपकरण 0 के साथ समाप्त होता है, इसलिए CI केवल इसलिए विफल नहीं होता कि मेल नहीं खाया; मेल न खाना समस्या गिना जाए या नहीं, यह आप --json पढ़कर तय करते हैं, जैसा ऊपर फ़िल्टर वाले अनुभाग में दिखाया गया है।
अगर कोई भी फ़ाइल अस्वीकार हो, तो यह चरण विफल होता है और लॉग में फ़ाइलों के नाम और कारण सूचीबद्ध करता है। तर्क पहले जैसा ही है: पहले एग्ज़िट कोड पढ़ें, विफलता पर मानक त्रुटि छापें, फिर jq से बैच रिपोर्ट में से reason निकालें। डायरेक्टरी आर्ग्युमेंट न हो तो यह तुरंत बाहर निकल जाती है, ताकि "$1"/*.pdf कभी /*.pdf में न फैले।
#!/usr/bin/env bash
# उपयोग: ./ci-step.sh docs
docs="${1:?usage: ./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 ख़ाली है |
jq में .files[]? के अंत का प्रश्नचिह्न एकल-फ़ाइल रिपोर्ट (जिसमें 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 है।