φυβλαςのβλογ
บล็อกของ phyblas



การใช้มอดูล subprocess ใน python เพื่อควบคุม shell
เขียนเมื่อ 2020/03/06 21:35
แก้ไขล่าสุด 2024/02/22 10:38


คำสั่งอะไรต่างๆหลากหลายในระดับสูงในคอมมักจะต้องทำโดยผ่านคอมมานด์ไลน์

เวลาที่เขียนโปรแกรมภาษาไพธอนงานบางอย่างที่ไม่ได้อยู่ในขอบเขตของคำสั่งที่มีอยู่แล้วในไพธอนก็อาจจำเป็นต้องใช้การสั่งงานในคอมมานด์ไลน์

ไพธอนสามารถควบคุมคอมมานด์ไลน์ได้ผ่านหลายวิธี หนึ่งในวิธีที่นิยมใช้ก็คือใช้มอดูล subprocess ซึ่งเป็นมอดูลที่มีติดตัวอยู่แล้วแต่แรกในไพธอน ไม่ต้องติดตั้งเพิ่มเติม

นอกจากนี้ยังมีวิธีเช่นใช้ฟังก์ชันในมอดูล os เช่นฟังก์ชัน os.system() หรือ os.popen() ซึ่งเมื่อก่อนอาจนิยมใช้ แต่ในปัจจุบันส่วนใหญ่คนมักจะแนะนำให้ใช้ subprocess แทนมากกว่า

ในบทความนี้จะอธิบายวิธีการใช้มอดูล subprocess เพื่อสั่งคำสั่งในคอมมานด์ไลน์

อนึ่งคอมมานด์ไลน์ใน mac หรือ linux จะเป็นเชลยูนิกซ์ ในขณะที่วินโดวส์เป็นคอมมานด์พร้อมปต์ (รายละเอียดอ่านใน https://phyblas.hinaboshi.com/20190124) ไม่ว่าจะแบบไหนก็สามารถใช้ subprocess ควบคุมได้เหมือนกัน ต่างกันที่คำสั่งต่างๆในเชล

บทความนี้จะยกตัวอย่างโดยใช้เชลลูนิกซ์ซึ่งใช้ใน mac หรือ linux เป็นหลัก เพราะโดยทั่วไปแล้วมีโอกาสได้ใช้งานมากกว่าในวินโดวน์มาก




การใช้คำสั่ง run

ภายในมอดูล subprocess ประกอบไปด้วยฟังก์ชันหลายตัวที่ใช้ควบคุมคอมมานด์ไลน์ ตัวที่เป็นพื้นฐานที่สุดก็คือ subprocess.run

การใช้ subprocess.run นั้นโดยพื้นฐานง่ายที่สุดก็คือ ถ้าเป็นคำสั่งเดี่ยวๆ ต้องการสั่งอะไรก็ใส่คำสั่งลงไปเลย เช่น
import subprocess
subprocess.run('pwd')

แล้วก็จะได้ผลการรันคำสั่งออกมา เช่นคำสั่ง pwd นี้จะให้ค้าออกมาเป็นตำแหน่งโฟลเดอร์ที่อยู่ปัจจุบัน เช่น
/home/phyblas

หากคำสั่งมีส่วนที่ต้องเว้นวรรค เช่นมีตัวเลือกเสริมหรือชื่อไฟล์ประกอบ ไม่ได้มีแค่คำสั่งเดี่ยวๆ เช่นแบบนี้ จะใส่ไปทั้งอย่างนั้นไม่ได้
subprocess.run('python -V') # ได้ FileNotFoundError: [Errno 2] No such file or directory: 'python -V': 'python -V'

ในตัวอย่างนี้เป็นการใช้คำสั่ง python ซึ่งใช้สำหรับรันไพธอนในคอมมานด์ไลน์เป็นตัวอย่าง (รายละเอียดการใช้อ่านใน https://phyblas.hinaboshi.com/20190705) โดยเติมตัวเลือกเสริม -V เป็นการแสดงเวอร์ชันของไพธอนที่ใช้อยู่ปัจจุบัน

กรณีที่มีตัวเลือกเสริมแบบนี้จะต้องใส่เป็นลิสต์โดยแยกส่วนของแต่ละส่วนออกจากกันแทน แบบนี้
subprocess.run(['python','-V'])

ได้
Python 3.8.1

แต่หากต้องการจะใส่ติดกันไปเลยเหมือนเวลาที่พิมพ์ในคอมมานด์ไลน์จริงๆให้ใส่คีย์เวิร์​ด shell=True ลงไป
subprocess.run('python -V',shell=True)

เมื่อใส่ shell=True ไปจะทำให้พิมพ์คำสั่งเหมือนเวลาเวลาอยู่ในคอมมานด์ไลน์โดยตรงเลย คือจะมีการแบ่งช่องว่างเว้นวรรคเป็นตัวแยก

หรือจะใช้ .split() เพื่อให้แยกสายอักขระเป็นลิสต์โดยเว้นตามช่องว่างให้เองก็ได้เช่นกัน
subprocess.run('python -V'.split())




ออบเจ็กต์ CompletedProcess ที่ได้จากการรันเสร็จ

ค่าคืนกลับที่ได้จาก subprocess.run คือออบเจ็กต์ CompletedProcess ซึ่งจะเก็บค่าอาร์กิวเมนต์ต่างๆที่เราใช้รัน (args) และโค้ดคืนกลับ (returncode) สามารถเก็บใส่ตัวแปรไว้แล้วเอาไว้มาดูค่าได้
p = subprocess.run('python -V',shell=True)
print(p) # ได้ CompletedProcess(args=['python', '-V'], returncode=0)
print(p.args) # ได้ ['python', '-V']
print(p.returncode) # ได้ 0

args ก็คือคำสั่งที่เราป้อนเข้าไป

returncode คือค่าที่แสดงผลว่ารันแล้วมีข้อผิดพลาดหรือไม่ หากไม่มีข้อผิดพลาดก็จะได้ 0 แต่หากเกิดข้อผิดพลาดก็จะได้รหัสของข้อผิดพลาดต่างกันไป

เช่นลองใส่ตัวเลือกเสริมที่ไม่มีอยู่จริงในคำสั่งนั้นเข้าไป
p = subprocess.run('python -j',shell=True)

ก็จะเกิดข้อผิดพลาด ขึ้นมาว่า
Unknown option: -j
usage: python [option] ... [-c cmd | -m mod | file | -] [arg] ...
Try `python -h' for more information.

จากนั้นถ้าลองมาดูค่าของ CompletedProcess ที่ได้มาก็จะได้ returncode เป็น 2 แบบนี้เป็นต้น
print(p) # ได้ CompletedProcess(args='python -j', returncode=2)




การทำให้เกิดข้อผิดพลาดตามเมื่อมีข้อผิดพลาดในคำสั่ง

จากตัวอย่างที่แล้วจะเห็นว่าแม้จะเกิดข้อผิดพลาดขึ้นในโค้ดของคอมมานด์ไลน์แบบนี้ก็ตาม ก็จะไม่ทำให้เกิดข้อผิดพลาดขึ้นมาจริงๆในโปรแกรมไพธอน

หากต้องการให้ข้อผิดพลาดนั้นกลายมาเป็นข้อผิดพลาดในไพธอน เพื่อให้โปรแกรมชะงักลงไปด้วยให้ใส่คีย์เวิร์ด check=True
subprocess.run('python -j',shell=True,check=True)

แบบนี้จะเกิดข้อผิดพลาด
CalledProcessError: Command 'python -j' returned non-zero exit status 2.




การเปลี่ยนโฟลเดอร์ที่รัน หรือเปลี่ยนตัวแปรสภาพแวดล้อม

ปกติคำสั่งจะถูกรันในโฟลเดอร์ที่อยู่ปัจจุบัน แต่หากต้องการกำหนดโฟลเดอร์ที่ต้องการรันก็ใส่คีย์เวิร์ด cwd
subprocess.run('ls',cwd='/home')

เพียงแต่ว่าในนี้จะใช้ ~ เพื่อแทนโฟลเดอร์บ้านไม่ได้ หากต้องการก็อาจต้องใช้ os.expandusers (รายละเอียดอ่านใน https://phyblas.hinaboshi.com/20200304)
import os
subprocess.run('ls',cwd=os.path.expanduser('~'))

ทั้ง ~ หรือการแทนค่าตัวแปรโดยขึ้นต้นด้วย $ นั้นจะได้ผลต่อเมื่อใช้ shell=True
subprocess.run(['echo HOME: $HOME'],shell=True)

ได้
HOME: /home/phyblas

ปกติเวลาที่รันคอมมานด์ไลน์ด้วยวิธีนี้ ค่าตัวแปรสภาพแวดล้อมจะเป็นไปตามที่ตั้งไว้แต่เดิม แต่ก็สามารถเปลี่ยนแปลงหรือเพิ่มเข้าไปได้โดยใส่คีย์เวิร์ด env เช่น
subprocess.run(['echo HOME: $HOME'],shell=True,env={'HOME': 'บ้านฉัน'})

ได้
HOME: บ้านฉัน




การกำหนดเวลาจำกัดให้คำสั่ง

หากใส่คีย์เวิร์ด timeout จะสามารถกำหนดเวลาจำกัดซึ่งถ้าคำสั่งใช้เวลาเลยกว่านั้นก็จะเกิดข้อผิดพลาดขึ้น

เช่นลองใช้คำสั่ง sleep เพื่อทำให้เกิดการรอจนทำให้สิ้นสุดการทำงานของคำสั่งไม่ทัน
subprocess.run('sleep 2',shell=True,timeout=1)

ก็จะเกิดข้อผิดพลาดแบบนี้ขึ้น
TimeoutExpired: Command 'sleep 2' timed out after 1 seconds




การเอาผลลัพธ์หรือข้อผิดพลาดที่ได้จากคำสั่ง

ปกติเมื่อใช้ subprocess.run จะแสดงผลที่ออกมาให้เห็นทันที แต่ผลที่ได้นั้นจะไม่ได้ถูกเก็บไว้ที่ไหน นำไปใช้ต่อไม่ได้

หากต้องการจะให้ผลลัพธ์ถูกเก็บไว้เพื่อใช้ทีหลัง แทนที่จะแสดงออกมาทันที แบบนี้ให้ใส่คีย์เวิร์ด stdout=subprocess.PIPE แล้วค่าจะถูกเก็บอยู่ใน .stdout
p = subprocess.run(['python', '-V'],stdout=subprocess.PIPE)
print(p) # ได้ CompletedProcess(args=['python', '-V'], returncode=0, stdout=b'Python 3.8.1\n')
print(p.stdout) # ได้ b'Python 3.8.1\n'

ค่าที่ได้คืนกลับมาจะเป็นข้อมูลแบบ bytes หากต้องการให้เป็นสายอักขระซึ่งเป็นยูนิโค้ดให้ใส่คีย์เวิร์ด encoding เข้าไป โดยทั่วไปก็ใช้ utf-8
p = subprocess.run(['python', '-V'],stdout=subprocess.PIPE,encoding='utf-8')
print(p.stdout) # ได้ Python 3.8.1

เพียงแต่ว่าคีย์เวิร์ด encoding นี้ใช้ได้ตั้งแต่ในไพธอน 3.6 ถ้าเป็นรุ่นเก่ากว่านั้นคือ 3.5 ลงมาอาจใช้เมธอด decode() เพื่อแปลงเป็นยูนิโค้ดอีกที แบบนี้
p = subprocess.run(['python', '-V'],stdout=subprocess.PIPE)
print(p.stdout.decode()) # ได้ Python 3.8.1

หรือถ้าเป็นไพธอน 3.7 ขึ้นไปจะใส่ text=True ก็ได้ แบบนี้ก็มีความหมายเหมือนกัน
p = subprocess.run(['python', '-V'],stdout=subprocess.PIPE,text=True)
print(p.stdout) # ได้ Python 3.8.1

แต่ถ้าเป็นไพธอน 3.6 ลงมาจะใช้ universal_newlines=True แทน
p = subprocess.run(['python', '-V'],stdout=subprocess.PIPE,universal_newlines=True)
print(p.stdout) # ได้ Python 3.8.1

subprocess.PIPE นี้จริงๆแล้วก็คือตัวเลขธรรมดาที่มีค่า -1 ดังนั้นจริงๆจะใส่เป็น stdout=-1 ไปก็ได้ แต่ปกติจะใส่เป็น subprocess.PIPE เพื่อให้เข้าใจความหมายได้ง่าย

หากต้องการให้ผลลัพธ์ถูกโยนทิ้งหายไปเลย ไม่ได้ถูกเก็บไว้ไหน และไม่ได้แสดงผลออกมาด้วย อาจใส่ stdout=subprocess.DEVNULL (หรือก็คือเลข -3)
p = subprocess.run(['python', '-V'],stdout=subprocess.DEVNULL)
print(p) # ได้ CompletedProcess(args=['python', '-V'], returncode=0)
print(p.stdout) # ได้ None

stdout นี้จะเอาแต่ค่าผลลัพธ์ทั่วไปเท่านั้น แต่กรณีที่เกิดข้อผิดพลาดผลจะไปออกที่ stderr
p = subprocess.run(['python', '-j'],stderr=subprocess.PIPE)
print(p) # ได้ CompletedProcess(args=['python', '-j'], returncode=2, stderr=b"Unknown option: -j\nusage: python [option] ... [-c cmd | -m mod | file | -] [arg] ...\nTry `python -h' for more information.\n")
print(p.stderr) # ได้ b"Unknown option: -j\nusage: python [option] ... [-c cmd | -m mod | file | -] [arg] ...\nTry `python -h' for more
print(p.stdout) # ได้ None

หากต้องการให้ผลของข้อผิดพลาดออกมาใน stdout ให้ใส่ stderr=subprocess.STDOUT (หรือก็คือเลข -2) และในขณะเดียวกันก็ใส่ stdout=subprocess.PIPE ด้วย
p = subprocess.run(['python', '-j'],stdout=subprocess.PIPE,stderr=subprocess.STDOUT)
print(p) # ได้ CompletedProcess(args=['python', '-j'], returncode=2, stdout=b"Unknown option: -j\nusage: python [option] ... [-c cmd | -m mod | file | -] [arg] ...\nTry `python -h' for more information.\n")
print(p.stderr) # ได้ None
print(p.stdout) # ได้ b"Unknown option: -j\nusage: python [option] ... [-c cmd | -m mod | file | -] [arg] ...\nTry `python -h' for more information.\n"

นอกจากนี้ในไพธอน 3.7 ขึ้นไปยังมีวิธีเขียนย่อให้ง่ายขึ้น คือใส่ capture_output=True แบบนี้ทั้งผลลัพธ์ปกติและผลจากข้อผิดพลาดก็จะถูกเก็บไว้ใน stdout และ stderr เหมือนกัน
p = subprocess.run(['python', '-V'],capture_output=True)
print(p.stdout) # ได้ b'Python 3.8.1\n'

p = subprocess.run(['python', '-j'],capture_output=True)
print(p.stderr[0]) # ได้ b"Unknown option: -j\nusage: python [option] ... [-c cmd | -m mod | file | -] [arg] ...\nTry `python -h' for more information.\n"




การเก็บผลลัพธ์ลงไฟล์

ถ้าต้องการให้ผลลัพธ์ที่ได้จากคำสั่งถูกใส่ลงไฟล์ก็อาจใช้ open เพื่อเปิดไฟล์สำหรับเขียนขึ้นมาแล้วใส่เป็น stdout

อาจใช้ with แบบนี้
with open('pythonver.txt','w') as f:
    subprocess.run(['python', '-V'],stdout=f)

หรืออาจใช้ open ใส่ลงไปใน stdout โดยตรงเลยก็ได้ คือเขียนแค่นี้
subprocess.run(['python', '-V'],stdout=open('pythonver.txt','w'))

ผลของข้อผิดพลาดก็เก็บลงไฟล์ได้เช่นกัน โดยใส่ที่ stderr เช่น
subprocess.run(['python', '-j'],stderr=open('pythonerr.txt','w'))

หรือ
subprocess.run(['python', '-j'],stdout=open('pythonerr.txt','w'),stderr=subprocess.STDOUT)




ค่าป้อนเข้าในคำสั่ง

คำสั่งบางอย่างต้องการค่าป้อนเข้า (input) เราอาจใส่ค่าลงไปในคีย์เวิร์ด input

ค่าที่ป้อนเข้านั้นต้องเป็นชนิด bytes นั่นคือสายอักขระที่มี b นำหน้า เพราะในไพธอน 3 ถ้าไม่ใส่ สายอักขระทั่วไปจะเป็นยูนิโค้ด (ถ้าเป็นไพธอน 2 สายอักขระธรรมดาจะเป็น bytes อยู่แล้ว ส่วน unicode ต้องเติม u)

เช่นลองป้อนคำสั่ง python สำหรับรันโค้ดไพธอน โดยใส่โค้ดที่เป็น
subprocess.run('python',input=b'print(1./11)')

ได้
0.09090909090909091

ถ้าต้องการใส่สายอักขระธรรมดาซึ่งเป็นยูนิโค้ดอยู่แล้วไม่ต้องใช้เป็น bytes ก็ให้เติม encoding='utf-8' หรือ text=True ไปด้วย ตัวอย่างข้างต้นอาจเขียนใหม่เป็นแบบนี้ก็ได้
subprocess.run('python',input='print(1./11)',encoding='utf-8')

หรือ
subprocess.run('python',input='print(1./11)',text=True)

ถ้าค่าป้อนเข้านั้นมาจากไฟล์ อาจใช้คีย์เวิร์ด stdin โดยใช้ open อ่านไฟล์เข้ามา เช่น
subprocess.run('python',stdin=open('sanpo.py'))

หรือจะใช้ open เปิดไฟล์เข้ามาแล้วอ่านด้วย .read() แล้วป้อนเข้าใส่คีย์เวิร์ด input ก็ได้
subprocess.run('python',input=open('sanpo.py').read(),text=True)




สรุปคีย์เวิร์ดใน run

คีย์เวิร์ด ความหมาย ค่าตั้งต้น หมายเหตุ
stdin กำหนดตัวป้อนเข้า None
input ค่าป้อนเข้า None
stdout กำหนดการแสดงผลค่าผลลัพธ์ทั่วไป None
stderr กำหนดการแสดงผลค่าผลลัพธ์ที่ผิดพลาด None
capture_output เก็บเอาทั้งค่าผลลัพธ์ทั่วไปและค่าผลลัพธ์ที่ผิดพลาด False เพิ่มมาในไพธอน 3.7
shell ถ้าเป็น False จะต้องป้อนค่าแยกเป็นลิสต์ False
cwd กำหนดโฟลเดอร์ที่รัน None
timeout กำหนดเวลาจำกัดในการรัน None
check ให้ขึ้นข้อผิดพลาดในไพธอนด้วยเมื่อคำสั่งมีข้อผิดพลาด False
encoding ใส่ encoding='utf-8' เพื่อให้ค่าป้อนเข้าและผลลัพธ์เป็นยูนิโค้ด None เพิ่มมาในไพธอน 3.6
text ถ้าเป็น True จะทำให้ทั้งค่าป้อนเข้าและผลลัพธ์เป็นยูนิโค้ด None เพิ่มมาในไพธอน 3.7
env กำหนดตัวแปรสภาพแวดล้อมเพิ่มเติมหรือเปลี่ยนแปลง None
universal_newlines เหมือน text แต่มีมาตั้งแต่ก่อนไพธอน 3.7 อาจใช้แทน text ในไพธอน 3.6 ลงมา




call, check_call, check_output

นอกจาก run แล้วก็มีคำสั่ง call, check_call, check_output ที่อาจสะดวกที่จะใช้แทนในงานบางอย่าง แม้ว่างานส่วนใหญ่แล้วใช้แค่ run ก็ทำได้หมด แต่ run นั้นเพิ่งมีตั้งแต่ไพธอน 3.5 ทำให้โค้ดเก่าๆอาจยังใช้ ๓ คำสั่งนี้ อีกทั้งยังอาจทำให้เขียนสั้นขึ้นกว่าด้วย

ที่น่าจะได้ใช้มากที่สุดคือ subprocess.check_output นั่นคือคำสั่งสำหรับรันแล้วคืนค่าเอาผลที่ได้มาใช้
res = subprocess.check_output(['python','-V'])
print(res) # ได้ b'Python 3.8.1\n'

ซึ่งถ้าเขียนแทนโดยใช้ run ก็จะเท่ากับการเขียนแบบนี้
res = subprocess.run(['python','-V'],stdout=subprocess.PIPE).stdout

ส่วน subprocess.call จะเป็นการรันแล้วคืนค่า returncode ซึ่งเป็นค่าที่บอกว่าเกิดข้อผิดพลาดหรือไม่ออกมา
returncode = subprocess.call(['python','-V'])
print(returncode) # ได้ 0

ซึ่งถ้าเขียนแทนด้วย run ก็จะได้
returncode = subprocess.run(['python','-V']).returncode

ส่วน check_call จะคล้ายกับ call แต่จะเหมือนกับเติม check=True

กรณีที่ไม่มีข้อผิดพลาดใดๆ ผลที่ได้ก็ไม่ต่างจาก call
returncode = subprocess.check_call(['python','-V'])
print(returncode) # ได้ 0

แต่เมื่อมีข้อผิดพลาดในคำสั่ง ผลที่ได้จะต่างกัน โดย call_check จะทำให้เกิดข้อผิดพลาดขึ้นมาในไพธอนด้วย
returncode = subprocess.call(['python','-j'])
print(returncode) # ได้ 2
returncode = subprocess.check_call(['python','-j'])
print(returncode) # ได้ CalledProcessError: Command '['python', '-j']' returned non-zero exit status 2.




Popen

ในตัวอย่างที่ผ่านมานั้นเป็นการสั่งคำสั่งแบบง่ายๆ คือสั่งครั้งเดียวแล้วเสร็จ รอผลลัพธ์เลย แต่สำหรับคำสั่งที่ต้องมีการใส่ข้อมูลเพิ่มเติมตอบโต้อย่างต่อเนื่องจะมีความซับซ้อนกว่า

subprocess.Popen เป็นออบเจ็กต์ที่มีไว้เพื่อสั่งคำสั่งลงในคอมมานด์ไลน์อย่างต่อเนื่อง

เนื่องจากมีรายละเอียดมากและเนื้อหาจะยาว ดังนั้นขอแยกไปเขียนต่อในอีกหน้า https://phyblas.hinaboshi.com/20200307




อ้างอิง


-----------------------------------------

囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧

ดูสถิติของหน้านี้

หมวดหมู่

-- คอมพิวเตอร์ >> เขียนโปรแกรม >> python
-- คอมพิวเตอร์ >> shell

ไม่อนุญาตให้นำเนื้อหาของบทความไปลงที่อื่นโดยไม่ได้ขออนุญาตโดยเด็ดขาด หากต้องการนำบางส่วนไปลงสามารถทำได้โดยต้องไม่ใช่การก๊อปแปะแต่ให้เปลี่ยนคำพูดเป็นของตัวเอง หรือไม่ก็เขียนในลักษณะการยกข้อความอ้างอิง และไม่ว่ากรณีไหนก็ตาม ต้องให้เครดิตพร้อมใส่ลิงก์ของทุกบทความที่มีการใช้เนื้อหาเสมอ

สารบัญ

รวมคำแปลวลีเด็ดจากญี่ปุ่น
มอดูลต่างๆ
-- numpy
-- matplotlib

-- pandas
-- manim
-- opencv
-- pyqt
-- pytorch
การเรียนรู้ของเครื่อง
-- โครงข่าย
     ประสาทเทียม
ภาษา javascript
ภาษา mongol
ภาษาศาสตร์
maya
ความน่าจะเป็น
บันทึกในญี่ปุ่น
บันทึกในจีน
-- บันทึกในปักกิ่ง
-- บันทึกในฮ่องกง
-- บันทึกในมาเก๊า
บันทึกในไต้หวัน
บันทึกในยุโรปเหนือ
บันทึกในประเทศอื่นๆ
qiita
บทความอื่นๆ

บทความแบ่งตามหมวด



ติดตามอัปเดตของบล็อกได้ที่แฟนเพจ

  ค้นหาบทความ

  บทความแนะนำ

ตัวอักษรกรีกและเปรียบเทียบการใช้งานในภาษากรีกโบราณและกรีกสมัยใหม่
ที่มาของอักษรไทยและความเกี่ยวพันกับอักษรอื่นๆในตระกูลอักษรพราหมี
การสร้างแบบจำลองสามมิติเป็นไฟล์ .obj วิธีการอย่างง่ายที่ไม่ว่าใครก็ลองทำได้ทันที
รวมรายชื่อนักร้องเพลงกวางตุ้ง
ภาษาจีนแบ่งเป็นสำเนียงอะไรบ้าง มีความแตกต่างกันมากแค่ไหน
ทำความเข้าใจระบอบประชาธิปไตยจากประวัติศาสตร์ความเป็นมา
เรียนรู้วิธีการใช้ regular expression (regex)
การใช้ unix shell เบื้องต้น ใน linux และ mac
g ในภาษาญี่ปุ่นออกเสียง "ก" หรือ "ง" กันแน่
ทำความรู้จักกับปัญญาประดิษฐ์และการเรียนรู้ของเครื่อง
ค้นพบระบบดาวเคราะห์ ๘ ดวง เบื้องหลังความสำเร็จคือปัญญาประดิษฐ์ (AI)
หอดูดาวโบราณปักกิ่ง ตอนที่ ๑: แท่นสังเกตการณ์และสวนดอกไม้
พิพิธภัณฑ์สถาปัตยกรรมโบราณปักกิ่ง
เที่ยวเมืองตานตง ล่องเรือในน่านน้ำเกาหลีเหนือ
ตระเวนเที่ยวตามรอยฉากของอนิเมะในญี่ปุ่น
เที่ยวชมหอดูดาวที่ฐานสังเกตการณ์ซิงหลง
ทำไมจึงไม่ควรเขียนวรรณยุกต์เวลาทับศัพท์ภาษาต่างประเทศ

บทความแต่ละเดือน

2024年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2023年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2022年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2021年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2020年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

ค้นบทความเก่ากว่านั้น

ไทย

日本語

中文