PPDF123
How-to2026-09-29อ่านประมาณ 5 นาที

ใช้ pdfx ในเทอร์มินัลและ CI: จากคำสั่งแรกสู่สคริปต์ที่ไว้ใจได้

ใช้ pdfx บนบรรทัดคำสั่งเพื่อรวม บีบอัด และใส่ลายน้ำ ประมวลผลหลายไฟล์พร้อมกัน และต่อเครื่องมือเป็นคำขอเดียวด้วย pipeline สำหรับสคริปต์และ CI ให้รู้ว่าอะไรควรพึ่งพา ได้แก่ exit code รายงาน --json อินพุตและเอาต์พุตมาตรฐาน และเหตุผลที่เครื่องมือกรองตามเงื่อนไขที่ไม่พบรายการตรงก็ยังจบด้วย 0

PDF123 · Updated 2026-09-29

ก่อนนำ pdfx ไปใส่ในสคริปต์หรือการทำ continuous integration (CI) ให้จำสองข้อ ข้อแรก exit code 0 ไม่ได้แปลว่ามีไฟล์ถูกเขียนเสมอไป เครื่องมือกรองตามเงื่อนไขที่ไม่พบรายการตรงก็จบด้วย 0 เช่นกัน ข้อสอง --json มีรูปแบบต่างกันระหว่างอินพุตเดียวกับหลายอินพุต สคริปต์ที่อ่านแค่อาร์เรย์ files จะไม่พบอะไรเลยเมื่อมีไฟล์ตรงเงื่อนไขเพียงไฟล์เดียว pdfx เป็นไคลเอนต์ HTTP ไฟล์ถูกอัปโหลดไปประมวลผลบนเซิร์ฟเวอร์ ไม่มี engine ในเครื่อง และไม่มีโหมดออฟไลน์ เอกสารอ้างอิงคำสั่งฉบับเต็มอยู่ในคู่มือ CLI บนหน้านักพัฒนา

แผนภาพ: การเรียก pdfx หนึ่งครั้งให้สามอย่างแก่สคริปต์ คือรายงาน JSON ทางเอาต์พุตมาตรฐาน ข้อความความล้มเหลวทางข้อผิดพลาดมาตรฐาน และ exit code สคริปต์อ่าน exit code ก่อน

ติดตั้ง แล้วรวมสองไฟล์

ต้องใช้ Node 20.3 ขึ้นไป หรือ Bun:

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

ถ้าไม่อยากติดตั้งแบบ global ให้ใส่ 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 ทันทีที่เสร็จ ไม่รอจนจบทั้งชุด ถ้าไฟล์หนึ่งล้มเหลว ไฟล์อื่นยังทำต่อ และ exit code ตอนจบงานชุดคือ 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 กลางงานชุด ไฟล์ที่เขียนไปแล้วยังอยู่ และ exit code คือ 130

เปิดไฟล์ที่เข้ารหัสด้วย --input-password PASSWORD เมื่องานชุดผสมไฟล์ที่ล็อกและไม่ล็อก ใช้ --password-for FILE=PASSWORD ซึ่งใส่ซ้ำได้

เรียกเครื่องมือแยกกันถ้าต้องการไฟล์ระหว่างทาง ไม่เช่นนั้นใช้ pipeline

ถ้าจะรวม ใส่ลายน้ำ แล้วบีบอัด และไม่ต้องการผลลัพธ์ระหว่างทางบนดิสก์ ให้พับเป็นคำขอเดียวด้วย pipeline ไฟล์ระหว่างทางจะไม่ถูกดาวน์โหลดแล้วอัปโหลดซ้ำ ถ้าต้องการไฟล์จากแต่ละขั้น ให้เรียกเครื่องมือแยกกัน 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

pipeline มีได้อย่างมาก 8 ขั้นตอน ขั้นที่ 9 จะได้ HTTP 400: at most 8 pipeline steps allowed เครื่องมือที่ต้องใช้ไฟล์ที่สอง (เช่น overlay-pdfs ที่ซ้อน PDF อีกไฟล์ทับ) ใช้เป็นขั้นของ pipeline ไม่ได้ pdfx จะปฏิเสธก่อนอัปโหลด ด้วย exit code 2 และข้อความ Tool "overlay-pdfs" needs a second file and cannot run as a pipeline step

ใช้อินพุตมาตรฐานเฉพาะเมื่อต้องการต่อคำสั่งเข้ากับ pipe อาร์กิวเมนต์ไฟล์ - อ่านจากอินพุตมาตรฐาน และ -o - เขียนผลลัพธ์ไปยังเอาต์พุตมาตรฐาน:

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

ในโหมดนี้ เอาต์พุตมาตรฐานมีเฉพาะไบต์ของไฟล์ ข้อความและข้อผิดพลาดไปที่ข้อผิดพลาดมาตรฐาน การเปลี่ยนเส้นทางจึงปลอดภัย เราตรวจกับ PDF ขนาดราว 1 KB แล้ว เอาต์พุตมาตรฐานเป็น PDF ขนาด 1040 ไบต์ เท่ากับไฟล์ที่เขียนด้วย -o และข้อผิดพลาดมาตรฐานว่างเปล่า เมื่ออ่านจากอินพุตมาตรฐานโดยไม่ใส่ -o ผลลัพธ์จะตั้งชื่อว่า stdin.pdf เขียนลงไดเรกทอรีปัจจุบันพร้อมหมายเหตุทางข้อผิดพลาดมาตรฐาน

สคริปต์ควรจับคู่ที่เหตุผล ไม่ใช่ข้อความ

Exit code ความหมาย ตัวอย่าง
0 สำเร็จ หรือเครื่องมือกรองตามเงื่อนไขที่ไม่พบรายการตรง รวมสำเร็จ เงื่อนไขของ filter-page-count เป็นเท็จ
1 ส่งคำขอแล้วแต่ล้มเหลว ในงานชุดคืออย่างน้อยหนึ่งไฟล์ล้มเหลว รหัสผ่านผิด ไม่ใช่ PDF หมดเวลา ไม่มีอะไรให้คืน
2 ใช้คำสั่งผิด ไม่มีอะไรถูกอัปโหลด ชื่อเครื่องมือสะกดผิด ขั้นของ pipeline ที่ต้องใช้ไฟล์ที่สอง ชนิดผลลัพธ์ขัดกับนามสกุลของไฟล์เอาต์พุต
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 วินาที

เมื่อเงื่อนไขเป็นเท็จ exit code ก็ยังเป็น 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 เมื่อทุกไฟล์ในงานชุดไม่ตรง exit code ก็ยังเป็น 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 ดังในหัวข้อเครื่องมือกรองด้านบน

ถ้ามีไฟล์ใดถูกปฏิเสธ ขั้นตอนนี้จะล้มและแสดงชื่อไฟล์กับเหตุผลในล็อก ตรรกะเหมือนเดิม คืออ่าน exit code ก่อน พิมพ์ข้อผิดพลาดมาตรฐานเมื่อล้มเหลว แล้วใช้ 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"

เรารันในเครื่องกับอินพุตสี่แบบ:

ไฟล์ในไดเรกทอรี Exit code ล็อก
PDF ที่ใช้ได้สองไฟล์ 0 ไม่มี
PDF ที่ใช้ได้สองไฟล์ กับไฟล์เสียหนึ่งไฟล์ 1 pdfx: broken.pdf: HTTP 400: ..., then a line broken.pdf invalid_pdf
PDF ที่ใช้ได้ไฟล์เดียว 0 ไม่มี report.json อยู่ในรูปแบบไฟล์เดียว
PDF ที่เสียไฟล์เดียว 1 reason: invalid_pdf, code: invalid_document and a hint line; report.json is empty

เครื่องหมายคำถามท้าย .files[]? ใน jq ป้องกันไม่ให้รายงานไฟล์เดียว (ซึ่งไม่มี files) ทำให้เกิดข้อผิดพลาด ไฟล์เสียไฟล์เดียวไม่มีรายงานงานชุด เหตุผลปรากฏเฉพาะใน errors.log จึงควรเก็บเอาต์พุตทั้งสองบรรทัดไว้

เมื่อนำไปใส่ใน CI ยังมีอีกสามเรื่องที่ควรตรวจสอบก่อน pdfx ต้องใช้เครือข่าย ไฟล์ถูกอัปโหลดไปที่ PDFX_API_BASE ซึ่งค่าเริ่มต้นคือ https://pdf123.xyz สำหรับเอกสารที่ต้องอยู่ในเครือข่ายของคุณ ให้รันบริการเองแล้วชี้ตัวแปรนี้ไปที่บริการนั้น ดู การโฮสต์เองให้อะไรจริง ๆ (และต้องแลกด้วยอะไร) เมื่อต้องการระบุตัวตน ให้ใส่คีย์ใน PDFX_API_KEY ไม่ใช่ในอาร์กิวเมนต์บรรทัดคำสั่ง เพราะจะค้างอยู่ในรายการโพรเซสและล็อก เวอร์ชันที่เผยแพร่ตอนนี้คือ 0.1.0 ใน CI ใช้ npx @pdf123/[email protected] ... เพื่อตรึงเวอร์ชัน ให้รูปแบบเอาต์พุตและ exit code ไม่เปลี่ยนตามรุ่นใหม่ และการอัปเกรดเป็นการเปลี่ยนที่คุณทำโดยตั้งใจ

หน้าของแพ็กเกจบน 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 MB สามรูปจึงกลายเป็น PDF 41.5 MB และรูปแนวตั้งออกมาอยู่บนหน้าแนวนอน ให้ย่อรูปก่อนแปลง แล้วหมุนหน้าที่ตะแคงภายหลัง