ฉันได้เขียนฟังก์ชั่น POSIX เปลือกซึ่งอาจนำมาใช้เพื่อท้องถิ่นใน namespace builtin เปลือกหรือฟังก์ชั่นในการใด ๆksh93
, dash
, mksh
หรือ(ชื่อเฉพาะเพราะผมได้รับการยืนยันโดยส่วนตัวแล้วก็ไปทำงานในทุกเหล่านี้)bash
จากกระสุนที่ฉันทดสอบมันล้มเหลวในการตอบสนองความคาดหวังของฉันyash
เท่านั้นและฉันไม่เคยคาดหวังว่ามันจะทำงานได้zsh
เลย posh
ผมไม่ได้ทดสอบ ฉันหมดหวังไประยะposh
หนึ่งแล้วและยังไม่ได้ติดตั้งในช่วงเวลาหนึ่ง อาจใช้งานได้ในposh
...
ฉันบอกว่ามันเป็น POSIX เพราะจากการอ่านข้อกำหนดฉันใช้ประโยชน์จากพฤติกรรมที่ระบุของยูทิลิตี้พื้นฐาน แต่เป็นที่ยอมรับว่าข้อกำหนดนั้นไม่ชัดเจนในเรื่องนี้และอย่างน้อยหนึ่งคนก็ไม่เห็นด้วยกับฉัน โดยทั่วไปฉันมีความไม่เห็นด้วยกับสิ่งนี้ในที่สุดฉันก็พบว่ามีข้อผิดพลาดเป็นของตัวเองและฉันอาจผิดในครั้งนี้เกี่ยวกับข้อกำหนดเช่นกัน แต่เมื่อฉันถามเขาต่อไปเขาไม่ตอบกลับ
อย่างที่ฉันบอกไปว่ามันใช้งานได้กับเปลือกหอยดังกล่าวอย่างแน่นอนและใช้งานได้จริงตามวิธีต่อไปนี้:
some_fn(){ x=3; echo "$x"; }
x=
x=local command eval some_fn
echo "${x:-empty}"
3
empty
command
คำสั่งระบุเป็นสาธารณูปโภคที่มีอยู่โดยทั่วไปและเป็นหนึ่งในก่อน$PATH
'd builtins หนึ่งในฟังก์ชั่นที่ระบุคือการห่อยูทิลิตี้บิวอินพิเศษในสภาพแวดล้อมของตัวเองเมื่อเรียกพวกเขาและ ...
{ sh -c ' x=5 set --; echo "$x"
x=6 command set --; echo "$x"
exec <""; echo uh_oh'
sh -c ' command exec <""; echo still here'
}
5
5
sh: 3: cannot open : No such file
sh: 1: cannot open : No such file
still here
... พฤติกรรมของการมอบหมายทั้งสองบรรทัดคำสั่งด้านบนนั้นถูกต้องตามข้อมูลจำเพาะ พฤติกรรมของเงื่อนไขข้อผิดพลาดทั้งสองก็ถูกต้องเช่นกันและในความเป็นจริงแล้วมีการทำซ้ำเกือบทั้งหมดจากข้อมูลจำเพาะ การกำหนดคำนำหน้าให้กับบรรทัดคำสั่งของฟังก์ชั่นหรือบิวด์อินพิเศษจะถูกระบุเพื่อส่งผลกระทบต่อสภาพแวดล้อมของเชลล์ในปัจจุบัน ในทำนองเดียวกันข้อผิดพลาดในการเปลี่ยนเส้นทางจะถูกระบุว่าร้ายแรงเมื่อชี้ไปที่ข้อใดข้อหนึ่ง command
ถูกระบุเพื่อระงับการดูแลพิเศษของ buildins พิเศษในกรณีเหล่านั้นและกรณีการเปลี่ยนทิศทางถูกแสดงโดยตัวอย่างจริงในสเปค
ปกติบิวด์อินcommand
จะถูกระบุให้ทำงานในสภาพแวดล้อม subshell - ซึ่งไม่ได้แปลว่ากระบวนการอื่นเพียงอย่างเดียวนั่นก็คือมันควรจะแยกไม่ออกจากพื้นฐาน ผลลัพธ์ของการเรียกบิวด์ปกติควรคล้ายกับสิ่งที่อาจได้รับจาก$PATH
คำสั่ง 'd ที่มีความสามารถคล้ายกัน และดังนั้น ...
na=not_applicable_to_read
na= read var1 na na var2 <<"" ; echo "$var1" "$na" "$var2"
word1 other words word2
word1 not_applicable_to_read word2
แต่command
คำสั่งไม่สามารถเรียกใช้ฟังก์ชันเชลล์ได้ดังนั้นจึงไม่สามารถใช้เพื่อแสดงผลการบําบัดแบบพิเศษที่มันสามารถสร้างได้ในตัวปกติ นั่นก็สเป็คเช่นกัน ในความเป็นจริง spec ระบุว่ายูทิลิตี้หลักของcommand
คือคุณสามารถใช้มันภายในฟังก์ชั่นเปลือกเสื้อคลุมที่มีชื่อสำหรับคำสั่งอื่นที่จะเรียกคำสั่งอื่นที่ไม่มีการเรียกซ้ำตัวเองเพราะมันจะไม่เรียกฟังก์ชั่น แบบนี้:
cd(){ command cd -- "$1"; }
หากคุณไม่ได้ใช้งานที่command
นั่นcd
ฟังก์ชั่นนี้ก็เกือบจะแน่นอนสำหรับการเรียกซ้ำตัวเอง
แต่เป็น builtin ปกติซึ่งสามารถเรียก builtins พิเศษcommand
สามารถทำได้ในสภาพแวดล้อม subshell ดังนั้นในขณะที่ปัจจุบันเปลือกรัฐกำหนดไว้ภายในอาจติดเปลือกปัจจุบัน - แน่นอนread
's $var1
และ$var2
ได้ - อย่างน้อยผลของการกำหนดบรรทัดคำสั่งอาจจะไม่ควร ...
คำสั่งง่ายๆ
หากไม่มีผลลัพธ์ชื่อคำสั่งหรือถ้าชื่อคำสั่งเป็นฟังก์ชันในตัวหรือพิเศษการกำหนดตัวแปรจะมีผลต่อสภาพแวดล้อมการดำเนินการปัจจุบัน มิฉะนั้นการกำหนดตัวแปรจะต้องถูกส่งออกสำหรับสภาพแวดล้อมการดำเนินการของคำสั่งและจะไม่ส่งผลกระทบต่อสภาพแวดล้อมการดำเนินการปัจจุบัน
ตอนนี้command
ความสามารถในการเป็นทั้ง builtin ปกติและการเรียก builtins พิเศษโดยตรงเป็นเพียงช่องโหว่ที่ไม่คาดคิดเกี่ยวกับ command-line ที่กำหนดฉันไม่ทราบ แต่ฉันรู้ว่าอย่างน้อยสี่ shells แล้ว กล่าวถึงเกียรติcommand
namespace
และถึงแม้ว่าcommand
จะไม่สามารถเรียกใช้ฟังก์ชันเชลล์ได้โดยตรง แต่สามารถเรียกได้eval
ตามที่แสดงและสามารถทำได้โดยอ้อม ดังนั้นฉันจึงสร้างเสื้อคลุมเนมสเปซบนแนวคิดนี้ ใช้รายการของการโต้แย้งเช่น:
ns any=assignments or otherwise=valid names which are not a command then all of its args
... ยกเว้นว่าคำดังกล่าวข้างต้นได้รับการยอมรับว่าเป็นหนึ่งในเพียงถ้ามันสามารถจะพบกับที่ว่างเปล่าcommand
$PATH
นอกจากนี้ในประเทศที่กำหนดขอบเขตตัวแปรเปลือกชื่อในบรรทัดคำสั่งก็ยังประเทศที่ขอบเขตตัวแปรทั้งหมดที่มีชื่อเดียวกรณีที่ต่ำกว่าตัวอักษรและรายชื่อของคนที่มาตรฐานอื่น ๆ เช่น$PS3
, $PS4
, $OPTARG
, $OPTIND
, $IFS
, $PATH
, $PWD
, $OLDPWD
และคนอื่น ๆ
และใช่โดยท้องถิ่นกำหนดขอบเขต$PWD
และ$OLDPWD
ตัวแปรและหลังจากนั้นอย่างชัดเจนcd
ไอเอ็นจีไป$OLDPWD
และ$PWD
ก็สามารถขอบเขตค่อนข้างน่าเชื่อถือไดเรกทอรีการทำงานปัจจุบันได้เป็นอย่างดี สิ่งนี้ไม่รับประกันแม้ว่าจะพยายามอย่างหนัก จะยังคงบ่งชี้สำหรับและเมื่อผลตอบแทนเป้าหมายห่อของมันไม่7<.
cd -P /dev/fd/7/
หากไดเรกทอรีการทำงานปัจจุบันได้รับการunlink()
'ในระหว่างกาลอย่างน้อยก็ควรจัดการอย่างน้อยเปลี่ยนกลับเป็น แต่จะปล่อยข้อผิดพลาดน่าเกลียดในกรณีที่แม้ว่า และเพราะมันยังคงบ่งผมไม่คิดว่าเคอร์เนลสติจะช่วยให้อุปกรณ์รากของมันที่จะเดินเท้าอย่างใดอย่างหนึ่ง(???)
นอกจากนี้ยังกำหนดขอบเขตตัวเลือกเชลล์ในเครื่องและกู้คืนสิ่งเหล่านี้กลับสู่สถานะที่พบเมื่อการคืนค่ายูทิลิตี้ที่ห่อหุ้ม มันถือว่าเป็นพิเศษในการที่จะรักษาสำเนาอยู่ในขอบเขตของตัวเองซึ่งตอนแรกกำหนดมูลค่าของการ$OPTS
$-
หลังจากจัดการกับการมอบหมายทั้งหมดในบรรทัดคำสั่งแล้วมันจะทำset -$OPTS
ก่อนที่จะเรียกใช้เป้าหมายการตัดคำ ด้วยวิธีนี้หากคุณกำหนด-$OPTS
ในบรรทัดคำสั่งคุณสามารถกำหนดตัวเลือกเชลล์ของเป้าหมายการตัดคำของคุณ เมื่อเป้าหมายกลับมาก็จะset +$- -$OPTS
มีสำเนาของตัวเอง$OPTS
(ซึ่งไม่ได้รับผลกระทบจากการกำหนดบรรทัดคำสั่ง)และเรียกคืนทั้งหมดกลับสู่สถานะดั้งเดิม
แน่นอนว่าไม่มีสิ่งใดที่จะหยุดผู้เรียกจากreturrn
การเข้าใช้ฟังก์ชันโดยวิธีการตัดเป้าหมายหรือการขัดแย้ง การทำเช่นนี้จะป้องกันการเรียกคืน / ล้างสถานะใด ๆ
ที่จะทำทุกสิ่งที่มันต้องผ่านไปสามeval
ลึก ก่อนอื่นมันจะล้อมตัวเองในขอบเขตของท้องถิ่นจากนั้นภายในจะอ่านในอาร์กิวเมนต์ตรวจสอบพวกเขาสำหรับชื่อเชลล์ที่ถูกต้องและออกจากข้อผิดพลาดหากพบว่ามันไม่ได้ หากข้อโต้แย้งทั้งหมดถูกต้องและในที่สุดก็เป็นหนึ่งในสาเหตุcommand -v "$1"
ที่จะกลับจริง(การเรียกคืน: $PATH
ว่างเปล่าที่จุดนี้)มันจะeval
กำหนดบรรทัดคำสั่งและส่งออกข้อโต้แย้งที่เหลืออยู่ทั้งหมดไปยังเป้าหมายห่อ(แม้ว่ามันจะไม่สนใจกรณีพิเศษสำหรับns
- เพราะ wouldn ว่า 'T จะมีประโยชน์มากและสามeval
ลึก s เป็นมากกว่าเพียงพอลึก)
มันใช้งานได้ดังนี้:
case $- in (*c*) ... # because set -c doesnt work
esac
_PATH=$PATH PATH= OPTS=$- some=vars \
command eval LOCALS=${list_of_LOCALS}'
for a do i=$((i+1)) # arg ref
if [ "$a" != ns ] && # ns ns would be silly
command -v "$a" &&
! alias "$a" # aliases are hard to run quoted
then eval " PATH=\$_PATH OTHERS=$DEFAULTS $v \
command eval '\''
shift $((i-1)) # leave only tgt in @
case $OPTS in (*different*)
set \"-\${OPTS}\" # init shell opts
esac
\"\$@\" # run simple command
set +$- -$OPTS "$?" # save return, restore opts
'\''"
cd -P /dev/fd/7/ # go whence we came
return "$(($??$?:$1))" # return >0 for cd else $1
else case $a in (*badname*) : get mad;;
# rest of arg sa${v}es
esac
fi; done
' 7<.
มีบางอย่างที่เปลี่ยนเส้นทางอื่น ๆ และและการทดสอบที่แปลกไม่กี่จะทำอย่างไรกับวิธีการที่เปลือกบางใส่อยู่c
ใน$-
นั้นปฏิเสธที่จะยอมรับว่ามันเป็นตัวเลือกให้กับset
(???)แต่ทั้งหมดขึ้นอยู่กับตนและใช้เป็นหลักเพียงเพื่อประหยัดจากเปล่ง ผลลัพธ์ที่ไม่ต้องการและคล้ายกันในกรณีขอบ และนั่นคือวิธีการทำงาน มันสามารถทำสิ่งเหล่านั้นเพราะมันตั้งค่าขอบเขตของตัวเองก่อนที่จะเรียกยูทิลิตี้ที่ถูกห่อหุ้มไว้ในที่ซ้อนกัน
มันยาวเพราะฉันพยายามระวังให้มากที่นี่ - สามevals
อย่างยาก แต่ด้วยสิ่งนี้คุณสามารถทำได้:
ns X=local . /dev/fd/0 <<""; echo "$X" "$Y"
X=still_local
Y=global
echo "$X" "$Y"
still_local global
global
การก้าวไปอีกขั้นและตั้งชื่อขอบเขตท้องถิ่นของยูทิลิตีที่ถูกห่อไว้อย่างต่อเนื่องไม่น่าจะยากนัก และตามที่เขียนไว้แล้วมันจะกำหนด$LOCALS
ตัวแปรให้กับโปรแกรมอรรถประโยชน์ที่ห่อซึ่งประกอบด้วยรายการที่คั่นด้วยช่องว่างของชื่อทั้งหมดที่กำหนดไว้ในสภาพแวดล้อมของโปรแกรมอรรถประโยชน์ที่ห่อไว้แล้ว
ชอบ:
ns var1=something var2= eval ' printf "%-10s%-10s%-10s%s\n" $LOCALS '
... ซึ่งปลอดภัยอย่างสมบูรณ์ - $IFS
ได้รับการทำให้บริสุทธิ์เป็นค่าเริ่มต้นและมีเพียงชื่อเชลล์ที่ถูกต้องเท่านั้นที่จะทำให้เป็นชื่อ$LOCALS
เว้นแต่คุณจะตั้งค่าตัวเองในบรรทัดคำสั่ง และแม้ว่าอาจจะมีตัวละครกลมในตัวแปรแยกคุณสามารถตั้งค่าOPTS=f
ในบรรทัดคำสั่งเช่นกันสำหรับยูทิลิตี้ที่ห่อหุ้มเพื่อห้ามการขยายตัวของพวกเขา ไม่ว่ากรณีใด ๆ:
LOCALS ARG0 ARGC HOME
IFS OLDPWD OPTARG OPTIND
OPTS PATH PS3 PS4
PWD a b c
d e f g
h i j k
l m n o
p q r s
t u v w
x y z _
bel bs cr esc
ht ff lf vt
lb dq ds rb
sq var1 var2
และนี่คือฟังก์ชั่น คำสั่งทั้งหมดนำหน้าด้วย w / \
เพื่อหลีกเลี่ยงalias
การขยาย:
ns(){ ${1+":"} return
case $- in
(c|"") ! set "OPTS=" "$@"
;; (*c*) ! set "OPTS=${-%c*}${-#*c}" "$@"
;; (*) set "OPTS=$-" "$@"
;; esac
OPTS=${1#*=} _PATH=$PATH PATH= LOCALS= lf='
' rb=\} sq=\' l= a= i=0 v= __=$_ IFS=" ""
" command eval LOCALS=\"LOCALS \
ARG0 ARGC HOME IFS OLDPWD OPTARG OPTIND OPTS \
PATH PS3 PS4 PWD a b c d e f g h i j k l m n \
o p q r s t u v w x y z _ bel bs cr esc ht ff \
lf vt lb dq ds rb sq'"
for a do i=$((i+1))
if \[ ns != "$a" ] &&
\command -v "$a" >&9 &&
! \alias "${a%%=*}" >&9 2>&9
then \eval 7>&- '\' \
'ARGC=$((-i+$#)) ARG0=$a HOME=~' \
'OLDPWD=$OLDPWD PATH=$_PATH IFS=$IFS' \
'OPTARG=$OPTARG PWD=$PWD OPTIND=1' \
'PS3=$PS3 _=$__ PS4=$PS4 LOCALS=$LOCALS' \
'a= b= c= d= e= f= g= i=0 j= k= l= m= n= o=' \
'p= q= r= s= t= u= v= w= x=0 y= z= ht=\ ' \
'cr=^M bs=^H ff=^L vt=^K esc=^[ bel=^G lf=$lf' \
'dq=\" sq=$sq ds=$ lb=\{ rb=\}' \''"$v' \
'\command eval 9>&2 2>&- '\' \
'\shift $((i-1));' \
'case \${OPTS##*[!A-Za-z]*} in' \
'(*[!c$OPTS]*) >&- 2>&9"'\' \
'\set -"${OPTS%c*}${OPTS#*c}"' \
';;esac; "$@" 2>&9 9>&-; PS4= ' \
'\set +"${-%c*}${-#*c}"'\'\" \
-'$OPTS \"\$?\"$sq";' \
' \cd -- "${OLDPWD:-$PWD}"
\cd -P ${ANDROID_SYSTEM+"/proc/self/fd/7"} /dev/fd/7/
\return "$(($??$?:$1))"
else case ${a%%=*} in
([0-9]*|""|*[!_[:alnum:]]*)
\printf "%s: \${$i}: Invalid name: %s\n" \
>&2 "$0: ns()" "'\''${a%%=*}'\''"
\return 2
;; ("$a") v="$v $a=\$$a"
;; (*) v="$v ${a%%=*}=\${$i#*=}"
;; esac
case " $LOCALS " in (*" ${a%%=*} "*)
;; (*) LOCALS=$LOCALS" ${a%%=*}"
;; esac
fi
done' 7<. 9<>/dev/null
}
( easiest thing ever )
ซึ่งสร้างหนึ่งเป็นเพียงเกี่ยวกับ แต่นั่นไม่ใช่สิ่งที่คุณต้องการ ฉันเดาว่าคุณสามารถทำได้( stuff in subshell; exec env ) | sed 's/^/namespace_/'
และeval
ผลลัพธ์ในเปลือกหลัก แต่ก็น่ารังเกียจ