φυβλαςのβλογ
บล็อกของ 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)
หอดูดาวโบราณปักกิ่ง ตอนที่ ๑: แท่นสังเกตการณ์และสวนดอกไม้
พิพิธภัณฑ์สถาปัตยกรรมโบราณปักกิ่ง
เที่ยวเมืองตานตง ล่องเรือในน่านน้ำเกาหลีเหนือ
ตระเวนเที่ยวตามรอยฉากของอนิเมะในญี่ปุ่น
เที่ยวชมหอดูดาวที่ฐานสังเกตการณ์ซิงหลง
ทำไมจึงไม่ควรเขียนวรรณยุกต์เวลาทับศัพท์ภาษาต่างประเทศ

ไทย

日本語

中文