มีวิธีรู้ไหมว่าผู้ใช้เรียกใช้โปรแกรมจาก bash ได้อย่างไร?

ปัญหาต่อไปนี้: ฉันมีสคริปต์นี้ foo.py และหากผู้ใช้เรียกใช้สคริปต์โดยไม่มีตัวเลือก --bar ฉันต้องการแสดงข้อความแสดงข้อผิดพลาดต่อไปนี้:

Please add the --bar option to your command, like so:
    python foo.py --bar

ตอนนี้ส่วนที่ยุ่งยากก็คือมีหลายวิธีที่ผู้ใช้อาจเรียกใช้คำสั่ง:

  • พวกเขาอาจใช้ python foo.py เหมือนในตัวอย่าง
  • พวกเขาอาจจะใช้ /usr/bin/foo.py
  • พวกเขาอาจมีเชลล์นามแฝง frob='python foo.py' และรันจริง ๆ frob
  • บางทีมันอาจเป็นนามแฝง git flab=!/usr/bin/foo.py และพวกเขาใช้ git flab

ในทุกกรณี ฉันต้องการให้ข้อความสะท้อนถึงวิธีที่ผู้ใช้เรียกใช้คำสั่ง เพื่อให้ตัวอย่างที่ฉันให้นั้นสมเหตุสมผล

sys.argv มี foo.py เสมอ และ /proc/$$/cmdline ไม่ทราบเกี่ยวกับนามแฝง สำหรับฉันดูเหมือนว่าแหล่งที่มาเดียวที่เป็นไปได้สำหรับข้อมูลนี้ก็คือการทุบตีเอง แต่ฉันไม่รู้จะถามอย่างไร

มีความคิดอะไรบ้าง?

อัปเดต แล้วถ้าเราจำกัดสถานการณ์ที่เป็นไปได้ไว้เฉพาะรายการข้างต้นล่ะ

อัปเดต 2: มีคนจำนวนมากเขียนคำอธิบายที่ดีมากเกี่ยวกับสาเหตุที่เป็นไปไม่ได้ในกรณีทั่วไป ดังนั้นฉันอยากจะจำกัดคำถามของฉันไว้เพียงสิ่งนี้:

ภายใต้สมมติฐานต่อไปนี้:

  • สคริปต์เริ่มต้นแบบโต้ตอบจากทุบตี
  • The script was start in one of these 3 ways:
    1. foo <args> where foo is a symbolic link /usr/bin/foo -> foo.py
    2. git foo โดยที่ alias.foo=!/usr/bin/foo ใน ~/.gitconfig
    3. git baz โดยที่ alias.baz=!/usr/bin/foo ใน ~/.gitconfig

มีวิธีแยกแยะระหว่าง 1 ถึง (2,3) จากภายในสคริปต์หรือไม่? มีวิธีแยกแยะระหว่าง 2 และ 3 จากภายในสคริปต์หรือไม่?

ฉันรู้ว่านี่เป็นเรื่องไกลตัว ดังนั้นตอนนี้ฉันจึงยอมรับคำตอบของชาร์ลส ดัฟฟี่

อัปเดต 3: จนถึงตอนนี้ Charles Duffy แนะนำมุมที่มีแนวโน้มมากที่สุดในความคิดเห็นด้านล่าง ถ้าฉันสามารถทำให้ผู้ใช้ของฉันมี

trap 'export LAST_BASH_COMMAND=$(history 1)' DEBUG

ใน .bashrc ของพวกเขา ฉันสามารถใช้สิ่งนี้ในโค้ดของฉัน:

like_so = None
cmd = os.environ['LAST_BASH_COMMAND']
if cmd is not None:
    cmd = cmd[8:]  # Remove the history counter
    if cmd.startswith("foo "):
        like_so = "foo --bar " + cmd[4:]
    elif cmd.startswith(r"git foo "):
        like_so = "git foo --bar " + cmd[8:]
    elif cmd.startswith(r"git baz "):
        like_so = "git baz --bar " + cmd[8:]
if like_so is not None:
    print("Please add the --bar option to your command, like so:")
    print("    " + like_so)
else:
    print("Please add the --bar option to your command.")

ด้วยวิธีนี้ ฉันจะแสดงข้อความทั่วไปหากฉันไม่สามารถรับวิธีการร้องขอได้ แน่นอน หากฉันต้องอาศัยการเปลี่ยนแปลงสภาพแวดล้อมของผู้ใช้ ฉันอาจตรวจสอบให้แน่ใจด้วยว่านามแฝงต่างๆ ส่งออกตัวแปรสภาพแวดล้อมของตัวเองที่ฉันสามารถดูได้ แต่อย่างน้อยวิธีนี้ทำให้ฉันสามารถใช้เทคนิคเดียวกันสำหรับ สคริปต์อื่นที่ฉันอาจเพิ่มในภายหลัง


person itsadok    schedule 12.07.2018    source แหล่งที่มา
comment
บางที history 1 อาจจะเพียงพอแล้วเหรอ?   -  person Aaron    schedule 12.07.2018
comment
หาก --bar เป็นข้อบังคับ ลองเพิ่มเข้าไปภายในตัวคุณเองอยู่เสมอ และจัดเตรียม --no-bar ให้กับผู้ใช้ที่มีทักษะมากกว่าที่ไม่ต้องการและรู้วิธีเพิ่มอาร์กิวเมนต์   -  person Mark Setchell    schedule 12.07.2018
comment
@ MarkSetchell ความพยายามของฉันในเรื่องทั่วไปกลับส่งผลย้อนกลับที่นี่ กรณีการใช้งานปัจจุบันของฉันเกี่ยวกับสคริปต์ที่ออก แต่ต้องการบอกผู้ใช้ว่าพวกเขาสามารถดำเนินการต่อด้วย --continue เหมือนกับเมื่อ git rebase พบข้อขัดแย้ง ดังนั้นความคิดของคุณจะไม่ได้ผลสำหรับฉัน   -  person itsadok    schedule 12.07.2018
comment
@แอรอน ใช่แล้ว! แต่ฉันสามารถเข้าถึงประวัติกระบวนการทุบตีพาเรนต์ได้อย่างไร   -  person itsadok    schedule 12.07.2018
comment
ฉันไม่แน่ใจว่าคุณหมายถึงอะไร ประวัติจะบันทึกสิ่งที่ผู้ใช้ป้อนในเชลล์ปัจจุบัน (และเชลล์อื่น ๆ ที่ประวัติยังคงอยู่ที่ ~/.bash_history) สิ่งนี้จะไม่บันทึกการทำงานของสคริปต์ของคุณในบางสถานการณ์ เช่น สคริปต์ที่ถูกเรียกจากอีกอันหนึ่ง (เฉพาะสคริปต์ที่ผู้ใช้เรียกเท่านั้นที่จะถูกบันทึก) หรือจาก cron หรือบริการอื่น ๆ ผู้ใช้ยังสามารถปิดการใช้งานได้   -  person Aaron    schedule 12.07.2018
comment
@Aaron ประวัติมักจะยังคงอยู่เฉพาะเมื่อ bash ออก เว้นแต่คุณจะใช้ลูกเล่นบางอย่างกับ $PROMPT_COMMAND ฉันสงสัยว่ามีวิธีสืบค้นประวัติของกระบวนการทุบตีที่ทำงานอยู่หรือไม่ ฉันไม่สนใจกรณีที่สิ่งอื่นนอกเหนือจาก bash แบบโต้ตอบทำให้เกิดกระบวนการ ยกเว้น git ซึ่งตัวมันเองนั้นกำเนิดจาก bash แบบโต้ตอบ   -  person itsadok    schedule 12.07.2018
comment
ไม่ใช่ว่าฉันรู้ขอโทษด้วย   -  person Aaron    schedule 12.07.2018
comment
แล้วถ้าผู้ใช้ใส่มันลงใน crontab ล่ะ? คุณไม่สามารถแสดงข้อความผู้ใช้ที่นั่นได้ คุณไม่ควรเพิ่มข้อความไปที่ /var/log/messages แทนใช่ไหม   -  person Dominique    schedule 18.07.2018
comment
แทนที่จะเบิร์นเป็นรอบ ๆ มากมายเกี่ยวกับวิธีการค้นหาชื่อของการร้องขอ (ซึ่ง IMHO มีหลายกรณีที่จะล้มเหลว) - ทำไมไม่ทำงานกับข้อความถึงผู้ใช้ล่ะ? Please add the --bar option to your command, like so: 'cmd --bar' คนส่วนใหญ่ฉลาดพอที่จะรู้ว่า cmd เป็นส่วนเติมเต็มสำหรับสิ่งที่พวกเขาพิมพ์   -  person dawg    schedule 18.07.2018
comment
โปรดทราบว่า คือ มีความเป็นไปได้ที่จะทำให้เชลล์ส่งออกสำเนาของ BASH_COMMAND ไปยังสภาพแวดล้อมจากกับดัก DEBUG แต่ถ้าคุณเชื่อถือสิ่งนั้น โปรแกรมของคุณจะมีพฤติกรรมในมือเมื่อถูกเรียกใช้โดย เปลือกเตรียมไว้อย่างดี...จึงดูไม่ค่อยมีประโยชน์   -  person Charles Duffy    schedule 19.07.2018
comment
@dawg แน่นอนว่านี่เป็นวิธีแก้ปัญหาปัจจุบันที่ฉันมี ฉันสงสัยว่ามีวิธีทำให้สะดวกยิ่งขึ้นหรือไม่   -  person itsadok    schedule 19.07.2018
comment
Re: การแก้ไข -- หากคุณต้องการเดินตามแผนผังกระบวนการและดูที่บรรทัดคำสั่งของผู้ปกครองจนกว่าคุณจะพบคำสั่ง git ใช่ คุณสามารถทำได้ ไม่พบนามแฝง Shell ที่ใดก็ได้ในประวัติ แต่เนื่องจากการเรียกใช้นามแฝง git ต้องใช้คำสั่งภายนอกจริง จึงจะปรากฏขึ้น   -  person Charles Duffy    schedule 19.07.2018
comment
(Re: symlinks วิธีมาตรฐาน argv[0] ใช้งานได้สำหรับพวกเขา โดยเป็นสื่อกลางผ่าน /proc/self/cmdline หรืออย่างอื่น)   -  person Charles Duffy    schedule 19.07.2018
comment
BTW บรรทัดคำสั่งแบบเต็มในขณะที่พิมพ์เป็นคำถามที่เกี่ยวข้องกันอย่างใกล้ชิด .   -  person Charles Duffy    schedule 19.07.2018
comment
ดูคำตอบที่อัปเดตของฉันด้านล่าง: ล้อมสคริปต์ python ในสคริปต์ bash ซึ่งให้ข้อความแสดงข้อผิดพลาดที่เหมาะสม โดยยึดตามรหัสทางออกที่ไม่ใช่ศูนย์ เป็นต้น   -  person philwalk    schedule 19.07.2018


คำตอบ (5)


ไม่ ไม่มีทางดูข้อความต้นฉบับได้ (ก่อนนามแฝง/ฟังก์ชัน/อื่นๆ)

การเริ่มต้นโปรแกรมใน UNIX ทำได้ดังนี้ที่ระดับ syscall พื้นฐาน:

int execve(const char *path, char *const argv[], char *const envp[]);

โดยเฉพาะอย่างยิ่งมีข้อโต้แย้งสามประการ:

  • เส้นทางสู่ปฏิบัติการ
  • อาร์เรย์ argv (รายการแรกที่ - argv[0] หรือ $0 - ถูกส่งผ่านไปยังไฟล์ปฏิบัติการนั้นเพื่อสะท้อนถึงชื่อที่มันเริ่มต้น)
  • รายการตัวแปรสภาพแวดล้อม

ไม่มีที่ใดในที่นี้ที่มีสตริงที่ให้คำสั่งเชลล์ที่ผู้ใช้ป้อนดั้งเดิมซึ่งร้องขอการเรียกใช้กระบวนการใหม่ นี่เป็นเรื่องจริงโดยเฉพาะอย่างยิ่งเนื่องจากไม่ใช่ว่าทุกโปรแกรมจะเริ่มต้นจากเชลล์เลย; พิจารณากรณีที่โปรแกรมของคุณเริ่มต้นจากสคริปต์ Python อื่นที่มี shell=False


เป็นเรื่องปกติโดยสิ้นเชิงบน UNIX ที่จะถือว่าโปรแกรมของคุณเริ่มต้นด้วยชื่อใดก็ตามที่ให้ไว้ใน argv[0]; สิ่งนี้ใช้ได้กับ symlink

คุณยังสามารถเห็นเครื่องมือ UNIX มาตรฐานที่ทำสิ่งนี้:

$ ls '*.txt'         # sample command to generate an error message; note "ls:" at the front
ls: *.txt: No such file or directory
$ (exec -a foobar ls '*.txt')   # again, but tell it that its name is "foobar"
foobar: *.txt: No such file or directory
$ alias somesuch=ls             # this **doesn't** happen with an alias
$ somesuch '*.txt'              # ...the program still sees its real name, not the alias!
ls: *.txt: No such file 

หากคุณต้องการต้องการสร้างบรรทัดคำสั่ง UNIX ให้ใช้ pipes.quote() (Python 2) หรือ shlex.quote() (Python 3) เพื่อความปลอดภัย

try:
    from pipes import quote # Python 2
except ImportError:
    from shlex import quote # Python 3

cmd = ' '.join(quote(s) for s in open('/proc/self/cmdline', 'r').read().split('\0')[:-1])
print("We were called as: {}".format(cmd))

อีกครั้ง สิ่งนี้จะไม่ "ยกเลิกการขยาย" นามแฝง เปลี่ยนกลับเป็นโค้ดที่ถูกเรียกใช้เพื่อเรียกใช้ฟังก์ชันที่เรียกใช้คำสั่งของคุณ ฯลฯ ไม่มีการยกเลิกการสั่นระฆังนั้น


สามารถนั้นสามารถใช้เพื่อค้นหาอินสแตนซ์ git ในแผนผังกระบวนการหลักของคุณและค้นพบรายการอาร์กิวเมนต์:

def find_cmdline(pid):
    return open('/proc/%d/cmdline' % (pid,), 'r').read().split('\0')[:-1]

def find_ppid(pid):
    stat_data = open('/proc/%d/stat' % (pid,), 'r').read()
    stat_data_sanitized = re.sub('[(]([^)]+)[)]', '_', stat_data)
    return int(stat_data_sanitized.split(' ')[3])

def all_parent_cmdlines(pid):
    while pid > 0:
        yield find_cmdline(pid)
        pid = find_ppid(pid)

def find_git_parent(pid):
    for cmdline in all_parent_cmdlines(pid):
        if cmdline[0] == 'git':
            return ' '.join(quote(s) for s in cmdline)
    return None
person Charles Duffy    schedule 18.07.2018
comment
@philwalk ถ้าฉันเข้าใจข้อเสนอของคุณอย่างถูกต้อง (การดึงประวัติจากสคริปต์) ก็มีความอ่อนไหว อย่างยิ่ง ต่อการกำหนดค่ารันไทม์ - การกำหนดค่าเชลล์จำนวนมากจะไม่เขียนประวัติลงในไฟล์จนกว่าจะออกเลย หากคุณควบคุมการกำหนดค่าเชลล์ของผู้ใช้ได้เพียงพอเพื่อให้แน่ใจว่าจะทำงานได้ คุณก็แค่ให้พวกเขาติดตั้งกับดัก DEBUG ที่ส่งออกสำเนาของ $BASH_COMMAND แล้วดำเนินการให้เสร็จสิ้น - person Charles Duffy; 19.07.2018
comment
@philwalk ...ยิ่งกว่านั้น มันค่อนข้างไม่เป็นความจริงเลยที่จะบอกว่าฉันได้ให้ไว้เพื่อพิสูจน์วิธีการบางอย่างที่ไม่ได้ผล - ฉันกำลังให้วิธีการที่ได้ผลในกรณีนามแฝง git (ฟังก์ชัน find_git_parent) และวิธีการที่ ทำงานในกรณี symlink (sys.argv[0] ซึ่ง - ตามที่ฉันสาธิต - เป็นแนวทางที่ใช้โดยเครื่องมือ UNIX มาตรฐาน) เป็น กรณีที่อนุญาตให้ใช้โซลูชันที่ไม่ขึ้นอยู่กับรายละเอียดของการกำหนดค่ารันไทม์ของเชลล์ - person Charles Duffy; 19.07.2018
comment
ฉันหมายถึงชื่อคำตอบของคุณไม่มีทางที่จะเห็นข้อความต้นฉบับ (ก่อนนามแฝง / ฟังก์ชั่น / ฯลฯ ) วิธีแก้ปัญหาของฉันทำอย่างนั้น - person philwalk; 20.07.2018
comment
@philwalk ใช่มั้ย? มันไม่ใช่วิธีแก้ปัญหา OP ต้องการดึงข้อความนั้น จากล่าม Python คุณสามารถดึงข้อมูลได้ จากเชลล์เดียวกันกับที่รันคำสั่ง เท่านั้น แม้แต่จากเชลล์ย่อยที่ตีความสคริปต์ โดยไม่ต้องมีส่วนร่วมอย่างแข็งขันของพาเรนต์เชลล์แบบโต้ตอบ - person Charles Duffy; 20.07.2018
comment
@philwalk ...เมื่อ aliasTest สคริปต์ของคุณสามารถบอกได้อย่างน่าเชื่อถือว่า ถ้าถูกเรียกใช้เองผ่านนามแฝง โดยไม่จำเป็นต้องให้เชลล์ทำการร้องขอนั้นให้มีการกำหนดค่าที่ไม่ใช่ค่าเริ่มต้นล่วงหน้า นั่นคือ< /b> เมื่อไหร่ที่ฉันจะประทับใจ -- และไม่ใช่เมื่อก่อน - person Charles Duffy; 20.07.2018
comment
คำถามเดิมสงสัยเป็นพิเศษว่าเป็นไปได้หรือไม่ที่จะรับข้อมูลจากสภาพแวดล้อมการทุบตี และคำถามตามคำพูดในปัจจุบันต้องการวิธีปรับแต่งข้อความแสดงข้อผิดพลาด และคำตอบของฉันก็ให้ข้อมูลที่จำเป็นในการดำเนินการนั้น - person philwalk; 20.07.2018
comment
@philwalk จาก สภาพแวดล้อม bash สู่ Python (ดังนั้นสคริปต์ Python จึงสามารถแก้ไขข้อความแสดงข้อผิดพลาดได้) - person Charles Duffy; 20.07.2018

ดูหมายเหตุที่ด้านล่างเกี่ยวกับสคริปต์ wrapper ที่เสนอในตอนแรก

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

ตัวอย่างเช่น หากผู้ใช้ต้องการเรียกสคริปต์หลาม 'myPyScript.py' ผ่านนามแฝง พวกเขาสามารถเปลี่ยนคำจำกัดความนามแฝงได้จากสิ่งนี้:

  alias myAlias='myPyScript.py $@'

สำหรับสิ่งนี้:

  alias myAlias='myPyScript.py --caller=myAlias $@'

หากพวกเขาต้องการเรียกสคริปต์ python จากเชลล์สคริปต์ ก็สามารถใช้ตัวเลือกบรรทัดคำสั่งเพิ่มเติมได้ดังนี้:

  #!/bin/bash
  exec myPyScript.py "$@" --caller=${0##*/}

การใช้งานแนวทางนี้ที่เป็นไปได้อื่นๆ:

  bash -c myPyScript.py --caller="bash -c myPyScript.py"

  myPyScript.py --caller=myPyScript.py

สำหรับการแสดงรายการบรรทัดคำสั่งแบบขยาย นี่คือสคริปต์ 'pyTest.py' ตามคำติชมของ @CharlesDuffy ซึ่งแสดงรายการ cmdline สำหรับสคริปต์ python ที่ทำงานอยู่ รวมถึงกระบวนการหลักที่สร้างสคริปต์นั้น หากใช้อาร์กิวเมนต์ -caller ใหม่ อาร์กิวเมนต์นั้นจะปรากฏในบรรทัดคำสั่ง แม้ว่านามแฝงจะถูกขยาย ฯลฯ

#!/usr/bin/env python

import os, re

with open ("/proc/self/stat", "r") as myfile:
  data = [x.strip() for x in str.split(myfile.readlines()[0],' ')]

pid = data[0]
ppid = data[3]

def commandLine(pid):
  with open ("/proc/"+pid+"/cmdline", "r") as myfile:
    return [x.strip() for x in str.split(myfile.readlines()[0],'\x00')][0:-1]

pid_cmdline = commandLine(pid)
ppid_cmdline = commandLine(ppid)

print "%r" % pid_cmdline
print "%r" % ppid_cmdline

หลังจากบันทึกสิ่งนี้ลงในไฟล์ชื่อ 'pytest.py' แล้วเรียกมันจากสคริปต์ทุบตีชื่อ 'pytest.sh' พร้อมอาร์กิวเมนต์ต่างๆ ผลลัพธ์ที่ได้คือ:

$ ./pytest.sh a b "c d" e
['python', './pytest.py']
['/bin/bash', './pytest.sh', 'a', 'b', 'c d', 'e']

หมายเหตุ: การวิพากษ์วิจารณ์สคริปต์ตัวตัดคำต้นฉบับ aliasTest.sh นั้นถูกต้อง แม้ว่าการมีอยู่ของนามแฝงที่กำหนดไว้ล่วงหน้าจะเป็นส่วนหนึ่งของข้อกำหนดของคำถาม และอาจสันนิษฐานได้ว่ามีอยู่ในสภาพแวดล้อมของผู้ใช้ ข้อเสนอก็กำหนดนามแฝง (สร้างความประทับใจที่ทำให้เข้าใจผิดว่าเป็นส่วนหนึ่งของคำแนะนำมากกว่าที่ระบุไว้ เป็นส่วนหนึ่งของสภาพแวดล้อมของผู้ใช้) และไม่ได้แสดงว่า wrapper จะสื่อสารกับสคริปต์ python ที่เรียกว่าอย่างไร ในทางปฏิบัติ ผู้ใช้จะต้องระบุแหล่งที่มาของ wrapper หรือกำหนดนามแฝงภายใน wrapper และสคริปต์ python จะต้องมอบหมายการพิมพ์ข้อความแสดงข้อผิดพลาดให้กับสคริปต์การโทรแบบกำหนดเองหลายสคริปต์ (ซึ่งมีข้อมูลการโทรอยู่) และไคลเอนต์ก็จะมี เพื่อเรียกสคริปต์ตัวตัดคำ การแก้ปัญหาเหล่านั้นนำไปสู่แนวทางที่ง่ายขึ้น ซึ่งสามารถขยายไปยังกรณีการใช้งานเพิ่มเติมจำนวนเท่าใดก็ได้

ต่อไปนี้เป็นสคริปต์ต้นฉบับเวอร์ชันที่สร้างความสับสนน้อยกว่าสำหรับการอ้างอิง:

#!/bin/bash
shopt -s expand_aliases
alias myAlias='myPyScript.py'

# called like this:
set -o history
myAlias $@
_EXITCODE=$?
CALL_HISTORY=( `history` )
_CALLING_MODE=${CALL_HISTORY[1]}

case "$_EXITCODE" in
0) # no error message required
  ;;
1)
  echo "customized error message #1 [$_CALLING_MODE]" 1>&2
  ;;
2)
  echo "customized error message #2 [$_CALLING_MODE]" 1>&2
  ;;
esac

นี่คือผลลัพธ์:

$ aliasTest.sh 1 2 3
['./myPyScript.py', '1', '2', '3']
customized error message #2 [myAlias]
person philwalk    schedule 18.07.2018
comment
ฉันกำลังพัฒนาเวอร์ชันที่รวมความคิดเห็นของคุณไว้ด้วย ขอบคุณ! - person philwalk; 19.07.2018
comment
โปรดทราบว่าประวัติไม่ได้ถูกจัดเก็บไว้เลยเสมอไป ตัวอย่างเช่น หากคำสั่งถูกเรียกใช้โดยมีช่องว่างก่อนหน้า จะไม่ถูกเก็บไว้ในประวัติ ในหน่วยความจำ ซึ่งจะไม่ถูกฟลัชลงดิสก์ด้วยซ้ำ HISTCONTROL ตั้งค่าเป็น nospace หรือ ignoreboth และแม้ว่าจะ ถูก เก็บไว้ การฟลัชหลังจากทุกคำสั่งเป็นสิ่งที่ต้องเปิดใช้งานอย่างชัดเจน และเนื่องจากเชลล์ที่ต่างกันมีประวัติที่แตกต่างกัน วิธีการนี้จึงจำเป็นต้องรู้ว่าเชลล์ใดที่ผู้ใช้ใช้งานอยู่เพื่อให้มีโอกาส - person Charles Duffy; 19.07.2018
comment
ฉันตื่นเต้นมากกับแนวคิดในการห่อสคริปต์ python ด้วยสคริปต์ bash แต่พบว่าสคริปต์ bash ไม่เห็นประวัติของผู้ปกครองเว้นแต่ฉันจะให้ผู้ใช้ค้นหาแหล่งที่มาของมันเสมอ ซึ่งฉันไม่เห็นว่าเป็นตัวเลือกที่ทำงานได้ . ดูเหมือนว่าคำแนะนำของ Charles Duffy เกี่ยวกับการใช้ DEBUG จะเป็นมุมมองที่มีแนวโน้มมากที่สุด - person itsadok; 22.07.2018
comment
@itsadok ... ในตัวอย่าง aliasTest.sh ด้านบน มันสร้างบรรทัดคำสั่งที่แน่นอนตามที่เรียกจากภายในสคริปต์ bash โดยไม่จำเป็นต้องจัดหาสคริปต์ใดๆ - person philwalk; 23.07.2018
comment
ทางเลือกที่ง่ายมาก หากคุณสามารถระบุนามแฝงหรือบันทึกวิธีการสร้างนามแฝงได้ ก็คือให้นามแฝงหรือสคริปต์ส่งผ่านอาร์กิวเมนต์ที่ระบุแท็กที่จำเป็นสำหรับข้อความแสดงข้อผิดพลาด หรือในทำนองเดียวกัน หากสร้างนามแฝงให้กับเชลล์สคริปต์ชื่อ fooForAlias.sh คุณสามารถระบุได้ใน sys.argv[0] โดยให้ข้อมูลที่ต้องการ - person philwalk; 24.07.2018
comment
@philwalk ใช้งานได้ใน aliasTest.sh เพราะคุณใช้ history คำสั่งจากกระบวนการ bash เดียวกันกับที่เรียกใช้คำสั่ง ไม่มีทางทราบได้ว่าสคริปต์ aliasTest.sh ถูกเรียกใช้อย่างไร - person itsadok; 24.07.2018
comment
@philwalk ...ซึ่งหมายความว่าหากโปรแกรมของคุณต้องการให้มีการจัดการข้อผิดพลาดประเภทนี้เมื่อเรียกใช้จากเชลล์ของผู้ใช้ คุณจะต้องมีโค้ดที่ทำงานอยู่ history ในเชลล์ของผู้ใช้เอง ไม่ใช่ใน สคริปต์ที่เชลล์เริ่มทำงาน ไม่ใช่แนวทางปฏิบัติที่สมเหตุสมผลในทางปฏิบัติหรือสุขอนามัยที่ดี หากทุกโปรแกรมพิมพ์เฉพาะข้อความแสดงข้อผิดพลาดที่ดีไปยังผู้ใช้ที่แก้ไขการกำหนดค่าเชลล์เพื่อรวมการเรียกใช้ เราคงจะยุ่งวุ่นวายกัน - person Charles Duffy; 25.07.2018
comment
@Charles Duffy ... ฉันจะต้องอัปเดตคำอธิบายเนื่องจากเห็นได้ชัดว่าไม่ชัดเจนว่าฉันกำลังเสนออะไร ... ลูกค้าต้องโทรผ่านสคริปต์ตัวตัดคำ ไม่ใช่จำลองว่ามันเรียกสคริปต์หลามอย่างไร ต้องใช้ Wrapper แยกต่างหากสำหรับแต่ละกรณีการใช้งาน - person philwalk; 26.07.2018
comment
ฉันไม่เห็นว่าการโทรผ่าน wrapper แก้ปัญหาได้อย่างไร - wrapper ไม่สามารถเข้าถึงประวัติของกระบวนการหลักได้ มีเพียงประวัติ ของตัวเอง เท่านั้น เว้นแต่ว่าเชลล์ของผู้ใช้จะมีการกำหนดค่าที่เฉพาะเจาะจงมาก ( ล้างประวัติลงดิสก์ทันทีในทุกคำสั่ง ซึ่งไม่ใช่พฤติกรรมเริ่มต้น) แต่ใช่ โปรดแก้ไขเพื่อสาธิต - person Charles Duffy; 26.07.2018
comment
นอกเหนือจาก: $@ ไม่มีประโยชน์ในนามแฝง -- นามแฝงเป็นเพียงการทดแทนคำนำหน้า ดังนั้นข้อความที่เหลือทั้งหมดจึงเสมอตามหลัง ดังนั้น alias foo='bar "$@"' และ foo one two three จึงเรียกใช้ bar "$@" one two three; คุณไม่สังเกตเห็นสิ่งนี้ในสถานการณ์ทั่วไป เพราะสำหรับเชลล์แบบโต้ตอบ $@ มักจะเป็นรายการองค์ประกอบที่เป็นศูนย์เสมอ (เว้นแต่ว่าคุณจะพยายามแก้ไขมัน เช่นเดียวกับ set -- "first argument" "second argument") - person Charles Duffy; 27.07.2018
comment
(และ $@ ที่ไม่มีเครื่องหมายคำพูดจะเหมือนกันกับ $* กล่าวคือจะละทิ้งความแตกต่างระหว่าง "first argument" "second argument" และ "first" "argument" "second" "argument") - person Charles Duffy; 27.07.2018
comment
...ย้อนกลับไปที่ประเด็น: หากคุณจะจะแก้ไขการเรียกเชลล์ คุณสามารถแก้ไขได้เพื่อตั้งค่า $0 ดังนั้นจึงไม่จำเป็นต้องมีอาร์กิวเมนต์บรรทัดคำสั่งเพิ่มเติมเลย ตัวอย่างเช่น: myAlias() { exec -a myAlias myPyScript "$@"; } จะเรียกใช้ myPyScript โดยมี myAlias ใน $0 - person Charles Duffy; 27.07.2018

ไม่มีทางที่จะแยกความแตกต่างระหว่างเวลาที่มีการระบุล่ามสำหรับสคริปต์อย่างชัดเจนในบรรทัดคำสั่ง และเมื่อระบบปฏิบัติการอนุมานจากบรรทัด hashbang

การพิสูจน์:

$ cat test.sh 
#!/usr/bin/env bash

ps -o command $$

$ bash ./test.sh 
COMMAND
bash ./test.sh

$ ./test.sh 
COMMAND
bash ./test.sh

การทำเช่นนี้จะป้องกันไม่ให้คุณตรวจพบความแตกต่างระหว่างสองกรณีแรกในรายการของคุณ

ฉันมั่นใจด้วยว่าไม่มีวิธีที่สมเหตุสมผลในการระบุวิธีอื่น ๆ (เป็นสื่อกลาง) ในการเรียกคำสั่ง

person Leon    schedule 18.07.2018
comment
ฉันยอมรับว่ามันเป็นไปไม่ได้ที่จะดึงข้อมูลบรรทัดคำสั่งเชลล์ดั้งเดิมอย่างแน่นหนา แต่ฉันไม่เห็นด้วยที่คุณพิสูจน์แล้วที่นี่ มีข้อมูลที่ดีกว่าใน /proc/self มากกว่าที่มีอยู่ใน ps มาก ตัวอย่างเช่น cmdline_for_pid() { local -a args; local arg; args=( ); while IFS= read -r -d '' arg; do args+=( "$arg" ); done </proc/"$1"/cmdline; printf '%q ' "${args[@]}"; echo; } กำหนดฟังก์ชัน cmdline_for_pid ที่ให้รายการที่ดีกว่า ps มาก - person Charles Duffy; 19.07.2018
comment
@CharlesDuffy สำหรับกรณีทดสอบของฉัน cmdline_for_pid ทำงานไม่ต่างจาก ps -o command - person Leon; 19.07.2018
comment
ใช้กรณีทดสอบที่น่าสนใจกว่านี้ -- กรณีที่มีการเว้นวรรค เครื่องหมายคำพูด ฯลฯ - person Charles Duffy; 19.07.2018
comment
@CharlesDuffy ฉันรู้ความแตกต่าง ฉันแค่อยากจะทราบว่าสำหรับภาพประกอบข้อความของฉัน cmdline_for_pid ไม่ได้เพิ่มอะไรที่เป็นประโยชน์ในขณะที่ต้องการให้ผู้อ่านถอดรหัสหลักฐาน - person Leon; 19.07.2018
comment
ฉันเข้าใจคำพิสูจน์ว่าหมายถึงบางสิ่งที่แตกต่างจากที่คุณตั้งใจไว้แล้ว ขณะที่ฉันอ่าน มีเพียงประเด็นเดียวที่ พิสูจน์ ว่าข้อมูลใดมีหรือไม่มีอยู่หากข้อมูลนั้นสะท้อนให้เห็นในรูปแบบที่สมบูรณ์และเป็นที่ยอมรับ การแปลที่สูญหายไม่ได้พิสูจน์สิ่งใดที่เป็นประโยชน์เกี่ยวกับสิ่งที่ สามารถ เรียกคืนมาได้โดยกระบวนการที่มีการสูญเสียน้อยกว่า และดังนั้นจึงไม่ได้พิสูจน์สิ่งใดเลยเกี่ยวกับข้อมูลที่เป็นอยู่หรือไม่มีให้เรียกคืนได้จริง - person Charles Duffy; 19.07.2018

ฉันเห็นสองวิธีในการทำเช่นนี้:

  • วิธีที่ง่ายที่สุด ตามที่ 3sky แนะนำ คือการแยกวิเคราะห์บรรทัดคำสั่งจากภายในสคริปต์ python argparse สามารถใช้เพื่อดำเนินการดังกล่าวได้อย่างน่าเชื่อถือ ใช้งานได้เฉพาะในกรณีที่คุณสามารถเปลี่ยนสคริปต์นั้นได้
  • วิธีที่ซับซ้อนกว่า ทั่วไปกว่าเล็กน้อยและเกี่ยวข้องคือการเปลี่ยนไฟล์ปฏิบัติการ python บนระบบของคุณ

เนื่องจากตัวเลือกแรกได้รับการบันทึกไว้อย่างดี ต่อไปนี้เป็นรายละเอียดเพิ่มเติมเล็กน้อยเกี่ยวกับตัวเลือกที่สอง:

ไม่ว่าสคริปต์ของคุณจะถูกเรียกด้วยวิธีใดก็ตาม python จะถูกรัน เป้าหมายคือการแทนที่ไฟล์ปฏิบัติการ python ด้วยสคริปต์ที่ตรวจสอบว่า foo.py อยู่ในอาร์กิวเมนต์หรือไม่ และหากเป็นเช่นนั้น ให้ตรวจสอบว่า --bar อยู่ด้วยหรือไม่ ถ้าไม่เช่นนั้น ให้พิมพ์ข้อความแล้วส่งคืน

ในกรณีอื่น ๆ ให้ดำเนินการปฏิบัติการ python จริง

หวังว่าตอนนี้การรัน python จะเสร็จสิ้นโดยใช้ shebang ต่อไปนี้: #!/usr/bin/env python3 หรือ trough python foo.py แทนที่จะเป็นตัวแปร #!/usr/bin/python หรือ /usr/bin/python foo.py ด้วยวิธีนี้ คุณสามารถเปลี่ยนตัวแปร $PATH และเติมไดเร็กทอรีที่มี false python อยู่ข้างหน้าได้

ในอีกกรณีหนึ่ง คุณสามารถแทนที่ /usr/bin/python executable ได้ โดยมีความเสี่ยงที่การอัปเดตจะเล่นได้ไม่ดี

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


ตัวอย่างสิ่งที่สามารถใช้เป็นสคริปต์ได้:

#!/usr/bin/env bash

function checkbar
{
    for i in "$@"
    do
            if [ "$i" = "--bar" ]
            then
                    echo "Well done, you added --bar!"
                    return 0
            fi
    done
    return 1
}

command=$(basename ${1:-none})
if [ $command = "foo.py" ]
then
    if ! checkbar "$@"
    then
        echo "Please add --bar to the command line, like so:"
        printf "%q " $0
        printf "%q " "$@"
        printf -- "--bar\n"
        exit 1
    fi
fi
/path/to/real/python "$@"

อย่างไรก็ตาม หลังจากอ่านคำถามของคุณอีกครั้ง เห็นได้ชัดว่าฉันเข้าใจผิด ในความคิดของฉัน มันเป็นเรื่องปกติที่จะพิมพ์ "foo.py ต้องถูกเรียกว่า foo.py --bar", "โปรดเพิ่มแถบในอาร์กิวเมนต์ของคุณ" หรือ "โปรดลอง (แทน )" โดยไม่คำนึงว่า ผู้ใช้ป้อน:

  • หากนั่นคือนามแฝง (git) นี่เป็นข้อผิดพลาดครั้งเดียว และผู้ใช้จะลองใช้นามแฝงของตนหลังจากสร้างมันขึ้นมา เพื่อให้พวกเขารู้ว่าจะต้องใส่ส่วน --bar ไว้ที่ไหน
  • with either with /usr/bin/foo.py or python foo.py:
    • If the user is not really command line-savvy, they can just paste the working command that is displayed, even if they don't know the difference
    • หากเป็นเช่นนั้น พวกเขาควรจะสามารถเข้าใจข้อความได้โดยไม่มีปัญหา และปรับบรรทัดคำสั่งได้
person MayeulC    schedule 18.07.2018
comment
"$@" ไม่ใช่ $@ Unquoted $@ ทำงานเหมือนกับ unquoted $* -- กล่าวคือ อาร์กิวเมนต์ของคุณทั้งหมดได้รับการแยกสตริงและขยายเป็น globs - person Charles Duffy; 19.07.2018
comment
และ exit -1 ไม่สมเหตุสมผล -- สถานะการออกของ UNIX เป็นจำนวนเต็ม unsigned (บวก) หนึ่งไบต์ และ [ ไม่ได้สัญญาว่า == จะทำงานได้เลย -- POSIX- เท่านั้น ตัวดำเนินการเปรียบเทียบสตริงที่เป็นมาตรฐานคือ = และ x นั้นไม่จำเป็น/ไร้จุดหมายเมื่อคุณอ้างอิงส่วนขยายของคุณและไม่ได้ใช้โหมด test ที่ติดธงล้าสมัย (-a และ -o แนะนำความกำกวมทางวากยสัมพันธ์ แต่ไม่มีอยู่ที่นี่) - person Charles Duffy; 19.07.2018
comment
ใช่ นั่นเป็นการรวบรวมอย่างรวดเร็วเพื่อเป็นตัวอย่าง และไม่ได้ทดสอบอย่างละเอียดถี่ถ้วน ฉันอยากจะอยู่อย่างปลอดภัยกับ x คุณพูดถูก ฉันจะเปลี่ยนทั้ง $@ เป็น "$@" บิตเครื่องหมายทำให้ค่าส่งคืนเป็น 255 แต่ฉันสามารถเปลี่ยนเป็น 1 ได้ เพราะมันไม่สำคัญมากนัก อย่างไรก็ตาม คำตอบนั้นจริง ๆ แล้วไม่ตรงประเด็น เนื่องจากไม่ได้ตอบข้อกังวลของ OP - person MayeulC; 19.07.2018

ฉันรู้ว่ามันเป็นงาน bash แต่ฉันคิดว่าวิธีที่ง่ายที่สุดคือแก้ไข 'foo.py' แน่นอนว่ามันขึ้นอยู่กับระดับของสคริปต์ที่ซับซ้อน แต่บางทีมันอาจจะพอดีก็ได้ นี่คือโค้ดตัวอย่าง:

#!/usr/bin/python

import sys

if len(sys.argv) > 1 and sys.argv[1] == '--bar':
    print 'make magic'
else:
    print 'Please add the --bar option to your command, like so:'
    print '    python foo.py --bar'

ในกรณีนี้ ไม่ว่าผู้ใช้จะเรียกใช้โค้ดนี้อย่างไร

$ ./a.py
Please add the --bar option to your command, like so:
    python foo.py --bar

$ ./a.py -dua
Please add the --bar option to your command, like so:
    python foo.py --bar

$ ./a.py --bar
make magic

$ python a.py --t
Please add the --bar option to your command, like so:
    python foo.py --bar

$ /home/3sky/test/a.py
Please add the --bar option to your command, like so:
    python foo.py --bar

$ alias a='python a.py'
$ a
Please add the --bar option to your command, like so:
    python foo.py --bar

$ a --bar
make magic
person 3sky    schedule 18.07.2018
comment
คำตอบของคุณไม่ได้ตอบคำถาม OPs แต่อย่างใด โซลูชันของคุณจะสร้างข้อความเดียวกันเสมอเมื่อไม่ได้ระบุตัวเลือก --bar ในขณะที่ OP ต้องการข้อความที่แตกต่างออกไป ขึ้นอยู่กับคำสั่งจริงที่ใช้ - person Leon; 18.07.2018