งูต้นฉบับร้อยลูกปัด argument ผ่านกรอบเครื่องหมายคำพูดไปเป็นสายคำสั่งเดียว

Python shlex.join: แสดงคำสั่ง shell ให้คนอ่านโดย quote argument อย่างถูกขอบเขต

พี่โชว์Engineering

เวลา log คำสั่งจาก subprocess เรามักทำ ' '.join(argv) แล้วได้ข้อความที่ copy ไปรันไม่ได้เมื่อ argument มีช่องว่าง อัญประกาศ หรือ $ shlex.join() ซึ่งมีตั้งแต่ Python 3.8 จะ quote รายการ argument ให้เป็นข้อความสำหรับ Unix shell และออกแบบให้ย้อนกลับด้วย shlex.split() ได้

from __future__ import annotations

import shlex
import unittest


def command_for_log(argv: list[str]) -> str:
    return shlex.join(argv)


class ShlexJoinTests(unittest.TestCase):
    def test_round_trip_special_arguments(self):
        argv = ["deploy", "--message", "ship safely", "$NOT_EXPANDED", "it's-ready"]
        rendered = command_for_log(argv)
        self.assertEqual(shlex.split(rendered), argv)

    def test_empty_argument_is_preserved(self):
        argv = ["tool", ""]
        self.assertEqual(shlex.split(shlex.join(argv)), argv)


if __name__ == "__main__":
    unittest.main()

ขอบเขตด้านความปลอดภัย

ใช้ shlex.join() เพื่อ แสดงผล command ที่มีอยู่แล้ว แต่ตอน execute ให้ส่ง list เข้า subprocess.run(argv, shell=False) ตามเดิม การเปลี่ยนกลับเป็น string แล้วเปิด shell=True เพิ่ม shell interpretation โดยไม่จำเป็น

เอกสารเตือนว่า quoting นี้ออกแบบสำหรับ Unix shells ไม่รับประกันกับ Windows shell หรือ shell ที่ไม่รองรับ POSIX ดังนั้น log สำหรับ PowerShell/cmd ควรใช้ renderer ที่ตรง platform และไม่ควรนำ string นี้ไปเป็น protocol ข้ามระบบ หาก log อาจมี token หรือ password ต้อง redact ที่ระดับ argument ก่อน join เพราะการ quote ไม่ได้ปกปิดความลับ

Recap

  • เก็บ argv เป็น list จนถึงจุด execute
  • ใช้ shlex.join() สำหรับข้อความที่คนอ่านหรือ copy
  • อย่าเปิด shell=True เพียงเพราะมี string ที่ quote แล้ว
  • redact secret ก่อน render และแยก renderer สำหรับ Windows