พารามิเตอร์ dd-style กับสคริปต์ทุบตี


19

ฉันต้องการส่ง params ไปที่ bash script สไตล์ dd โดยทั่วไปฉันต้องการ

./script a=1 b=43

เพื่อให้มีผลเช่นเดียวกับ

a=1 b=43 ./script

ฉันคิดว่าฉันสามารถทำสิ่งนี้ได้ด้วย:

for arg in "$@"; do
   eval "$arg";
done

เป็นวิธีที่ดีในการตรวจสอบว่าevalมีความปลอดภัยคือที่"$arg"ตรงกับแบบคงที่ (ไม่ใช้รหัส) การกำหนดตัวแปร?

หรือมีวิธีที่ดีกว่าในการทำเช่นนี้? (ฉันต้องการที่จะให้ง่ายนี้)


สิ่งนี้ถูกติดแท็กด้วย bash คุณต้องการโซลูชันที่สอดคล้องกับ Posix หรือคุณจะยอมรับโซลูชันทุบตีหรือไม่?
rici

อะไรแท็กบอกว่าเป็นสิ่งที่ฉันหมายความ :)
PSkocik

ทีนี้คุณก็สามารถแยกมันเป็นลวดลายด้วย=ตัวคั่นและทำการกำหนดด้วย eval ที่สร้างขึ้นอย่างระมัดระวังมากขึ้น เพียงเพื่อความปลอดภัยสำหรับการใช้งานส่วนตัวฉันต้องการมันในขณะที่คุณทำมัน
orion

คำตอบ:


16

คุณสามารถทำได้ด้วยการทุบตีโดยไม่มีการ eval (และไม่มีการหลบหนีเทียม):

for arg in "$@"; do
  if [[ $arg =~ ^[[:alpha:]_][[:alnum:]_]*= ]]; then
    declare +i +a +A "$arg"
  fi
done

แก้ไข:จากความคิดเห็นของStéphane Chazelas ฉันได้เพิ่มค่าสถานะลงในการประกาศเพื่อหลีกเลี่ยงการกำหนดตัวแปรที่ได้รับการประกาศแล้วว่าเป็นอาร์เรย์หรือตัวแปรจำนวนเต็มซึ่งจะหลีกเลี่ยงจำนวนเคสที่declareจะประเมินค่าส่วนของkey=valอาร์กิวเมนต์ ( +aจะทำให้เกิดข้อผิดพลาดหากตัวแปรที่จะตั้งค่าได้รับการประกาศแล้วว่าเป็นตัวแปรอาเรย์เป็นต้น) ช่องโหว่ทั้งหมดเหล่านี้เกี่ยวข้องกับการใช้ไวยากรณ์นี้เพื่อกำหนดตัวแปรที่มีอยู่ (อาเรย์หรือจำนวนเต็ม) ซึ่งโดยทั่วไปจะเป็นที่รู้จักกันดี ตัวแปรเชลล์

ในความเป็นจริงนี่เป็นเพียงตัวอย่างของการโจมตีแบบฉีดซึ่งจะมีผลต่อevalการแก้ปัญหาแบบเท่า ๆ กัน: มันจะดีกว่ามากที่จะอนุญาตเฉพาะชื่ออาร์กิวเมนต์ที่รู้จักมากกว่าการตั้งค่าตัวแปรใดก็ตามที่ปรากฏในบรรทัดคำสั่ง (พิจารณาสิ่งที่เกิดขึ้นหากชุดบรรทัดคำสั่งPATHตัวอย่างเช่นหรือรีเซ็ตPS1เพื่อรวมการประเมินผลบางอย่างซึ่งจะเกิดขึ้นในการแสดงพรอมต์ถัดไป)

แทนที่จะใช้ตัวแปร bash ฉันต้องการใช้อาเรย์แบบเชื่อมโยงของอาร์กิวเมนต์ที่มีชื่อซึ่งง่ายต่อการตั้งค่าและปลอดภัยกว่ามาก หรืออาจตั้งค่าตัวแปรทุบตีจริง แต่ถ้าชื่อของพวกเขาอยู่ในอาร์เรย์ที่เชื่อมโยงของอาร์กิวเมนต์ที่ถูกต้องตามกฎหมาย

เป็นตัวอย่างของวิธีการหลัง:

# Could use this array for default values, too.
declare -A options=([bs]= [if]= [of]=)
for arg in "$@"; do
  # Make sure that it is an assignment.
  # -v is not an option for many bash versions
  if [[ $arg =~ ^[[:alpha:]_][[:alnum:]_]*= &&
        ${options[${arg%%=*}]+ok} == ok ]]; then
    declare "$arg"
    # or, to put it into the options array
    # options[${arg%%=*}]=${arg#*=}
  fi
done

1
Regex ดูเหมือนว่าจะมีวงเล็บที่ไม่ถูกต้อง บางทีอาจจะใช้วิธีนี้แทน: ^[[:alpha:]_][[:alnum:]_]*=?
lcd047

1
@ lcd047: foo=เป็นวิธีเดียวที่จะตั้งค่า foo ให้เป็นสตริงว่างดังนั้นควรอนุญาต (IMHO) ฉันซ่อมวงเล็บขอบคุณ
rici

3
declareมันอันตรายพอ ๆ กับeval(อย่างใดอย่างหนึ่งอาจพูดได้แย่ลงเพราะมันไม่ชัดเจนว่ามันอันตรายเท่า) ลองใช้ตัวอย่างเพื่อเรียกสิ่งนั้นว่า'DIRSTACK=($(echo rm -rf ~))'เป็นอาร์กิวเมนต์
Stéphane Chazelas

1
@PSkocik: +xคือ "ไม่-x" -a= อาร์เรย์ดัชนี, -A= อาร์เรย์ที่เชื่อมโยง, -i= ตัวแปรจำนวนเต็ม ดังนั้น: ไม่ใช่อาเรย์ที่จัดทำดัชนีไม่ใช่อาเรย์ที่สัมพันธ์กันไม่ใช่จำนวนเต็ม
lcd047

1
โปรดทราบว่าในรุ่นถัดไปของbashคุณอาจต้องเพิ่ม+cเพื่อปิดการใช้งานตัวแปรผสมหรือ+Fปิดการใช้งานคนลอย ฉันยังคงใช้evalที่คุณรู้ว่าคุณยืนอยู่ที่ไหน
Stéphane Chazelas

9

POSIX หนึ่งชุด ( $<prefix>varแทน$varการหลีกเลี่ยงปัญหากับตัวแปรพิเศษเช่นIFS/ PATH... ):

prefix=my_prefix_
for var do
  case $var in
    (*=*)
       case ${var%%=*} in
         "" | *[!abcdefghijiklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_]*) ;;
         (*) eval "$prefix${var%%=*}="'${var#*=}'
       esac
  esac
done

เรียกว่าเป็นmyscript x=1 PATH=/tmp/evil %=3 blah '=foo' 1=2มันจะมอบหมาย:

my_prefix_x <= 1
my_prefix_PATH <= /tmp/evil
my_prefix_1 <= 2

6

โซลูชันของ lcd047 ได้รับการปรับปรุงใหม่ด้วยDD_OPT_คำนำหน้าฮาร์ดโค้ด:

while [[ $1 =~ ^[[:alpha:]_][[:alnum:]_]*= ]]; do
  eval "DD_OPT_${1%%=*}"='${1#*=}'; shift;
done

Frostschutzสมควรได้รับเครดิตสำหรับการฟื้นฟูส่วนใหญ่

ฉันวางไว้ในไฟล์ต้นฉบับที่มีตัวแปรเป็นสากล:

DD_OPTS_PARSE=$(cat <<'EOF'
  while [[ $1 =~ ^[[:alpha:]_][[:alnum:]_]*= ]]; do
    eval "DD_OPT_${1%%=*}"='${1#*=}'; shift;
  done
EOF
)

eval "$DD_OPTS_PARSE" มายากลทั้งหมด

รุ่นสำหรับฟังก์ชั่นจะเป็น:

DD_OPTS_PARSE_LOCAL="${PARSE_AND_REMOVE_DD_OPTS/DD_OPT_/local DD_OPT_}"

ในการใช้งาน:

eval "$DD_OPTS_PARSE_LOCAL"

ฉันทำrepoจากสิ่งนี้พร้อมการทดสอบและ README.md จากนั้นฉันก็ใช้สิ่งนี้ใน Github API CLI wrapper ที่ฉันเขียนและฉันก็ใช้ wrapper ตัวเดียวกันในการเซ็ต github clone ของrepo ที่กล่าวมา(bootstrapping สนุกมาก)

พารามิเตอร์ที่ปลอดภัยผ่านสำหรับสคริปต์ทุบตีในหนึ่งบรรทัด สนุก. :)


1
แต่คุณสามารถกำจัด*=*และหยุดการแทนที่คีย์ / val โดยที่ไม่มี = (ตั้งแต่คุณทำการปรับโครงสร้างใหม่): P
frostschutz

1
ในความเป็นจริงคุณสามารถกำจัดห่วงและถ้าใช้งานในขณะ $ 1 แทนเนื่องจากคุณกำลังขยับและทุก ...
frostschutz

1
เฮ้พิสูจน์ได้ว่าการระดมสมองได้ผล :)
lcd047

1
ความคิดในตอนเช้าเก็บเกี่ยว: คุณยังสามารถกำจัดkeyและและเพียงแค่เขียนval eval "${1%%=*}"=\${1#*=}แต่มันก็ยังสวยอยู่เหมือนที่eval "$1"@Rici ใช้declare "$arg"ไม่ได้ นอกจากนี้ระวังของการตั้งค่าสิ่งที่ต้องการหรือPATH PS1
lcd047

1
ขอบคุณ - ฉันคิดว่านั่นเป็นตัวแปรที่ประเมินแล้ว ฉันขอขอบคุณที่กรุณาอดทนรอกับฉัน - มันค่อนข้างแจ่มชัด อย่างไรก็ตามไม่มีอื่นนอกจากที่คิดไว้มันดูดี คุณรู้ว่าคุณสามารถขยายการทำงานในเปลือกใด ๆ caseกับ อาจเป็นเรื่องไม่สำคัญ แต่ในกรณีที่คุณไม่ทราบ ...
mikeserv

5

คลาสสิกบอร์นเชลล์ได้รับการสนับสนุนและทุบตีและเปลือกกรยังคงสนับสนุนเป็น-kตัวเลือก เมื่อมีผลบังคับใช้ddตัวเลือกคำสั่ง '-like ' ใด ๆ ที่ใดก็ได้บนบรรทัดคำสั่งจะถูกแปลงเป็นตัวแปรสภาพแวดล้อมที่ส่งไปยังคำสั่งโดยอัตโนมัติ:

$ set -k
$ echo a=1 b=2 c=3
$ 

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

$ set -k
$ env | grep '^[a-z]='   # No environment a, b, c
$ bash -c 'echo "Args: $*" >&2; env' a=1 b=2 c=3 | grep '^[a-z]='
Args: 
a=1
b=2
c=3
$ set +k
$ bash -c 'echo "Args: $*" >&2; env' a=1 b=2 c=3 | grep '^[a-z]='
Args: b=2 c=3
$

ครั้งแรกenv | grepแสดงให้เห็นถึงไม่มีตัวแปรสภาพแวดล้อมด้วยอักษรตัวเล็กกรณีเดียว ครั้งแรกbashแสดงให้เห็นว่าไม่มีข้อโต้แย้งที่ส่งผ่านไปยังสคริปต์ที่ดำเนินการผ่าน-cและสภาพแวดล้อมที่มีตัวแปรตัวอักษรสามตัว set +kยกเลิก-kและแสดงให้เห็นว่าคำสั่งเดียวกันขณะนี้ได้ข้อโต้แย้งผ่านไป (สิ่งa=1นั้นได้รับการปฏิบัติเหมือน$0สคริปต์; คุณสามารถพิสูจน์ได้ว่าด้วยการสะท้อนที่เหมาะสม)

นี้ประสบความสำเร็จในสิ่งที่คำถามที่ถาม - ที่พิมพ์ควรจะเป็นเช่นเดียวกับการพิมพ์./script.sh a=1 b=2a=1 b=2 ./script.sh

โปรดระวังว่าคุณประสบปัญหาหากคุณลองใช้เทคนิคเช่นนี้ในสคริปต์:

if [ -z "$already_invoked_with_minus_k" ]
then set -k; exec "$0" "$@" already_invoked_with_minus_k=1
fi

การ"$@"ปฏิบัติต่อคำต่อคำ; มันไม่ได้วิเคราะห์อีกครั้งเพื่อค้นหาตัวแปรสไตล์การมอบหมาย (ทั้งในbashและksh) ฉันเหนื่อย:

#!/bin/bash

echo "BEFORE"
echo "Arguments:"
al "$@"
echo "Environment:"
env | grep -E '^([a-z]|already_invoked_with_minus_k)='
if [ -z "$already_invoked_with_minus_k" ]
then set -k; exec "$0" "$@" already_invoked_with_minus_k=1
fi

echo "AFTER"
echo "Arguments:"
al "$@"
echo "Environment:"
env | grep -E '^([a-z]|already_invoked_with_minus_k)='

unset already_invoked_with_minus_k

และalready_invoked_with_minus_kตัวแปรสภาพแวดล้อมเท่านั้นที่ถูกตั้งค่าในexec'd script


คำตอบที่ดีมาก! เป็นที่น่าสนใจว่าจะไม่เปลี่ยน PATH แม้ว่า HOME สามารถเปลี่ยนได้ดังนั้นจะต้องมีสิ่งที่เหมือนบัญชีดำ (มี PATH อย่างน้อย) ของ env vars ที่อาจเป็นอันตรายเกินกว่าที่จะจัดวางในลักษณะนี้ ฉันชอบที่นี่สั้นเป็นพิเศษและตอบคำถาม แต่ฉันจะไปกับคำนำหน้า sanitize + eval + เพราะมันยังปลอดภัยและใช้งานได้ในระดับสากลมากขึ้น (ในสภาพแวดล้อมที่คุณไม่ต้องการให้ผู้ใช้ยุ่งกับสภาพแวดล้อม ) ขอขอบคุณและ +1
PSkocik

2

ความพยายามของฉัน:

#! /usr/bin/env bash
name='^[a-zA-Z][a-zA-Z0-9_]*$'
count=0
for arg in "$@"; do
    case "$arg" in
        *=*)
            key=${arg%%=*}
            val=${arg#*=}

            [[ "$key" =~ $name ]] && { let count++; eval "$key"=\$val; } || break

            # show time
            if [[ "$key" =~ $name ]]; then
                eval "out=\${$key}"
                printf '|%s| <-- |%s|\n' "$key" "$out"
            fi
            ;;
        *)
            break
            ;;
    esac
done
shift $count

# show time again   
printf 'arg: |%s|\n' "$@"

ใช้งานได้กับ (เกือบ) ขยะเองบน RHS:

$ ./assign.sh Foo_Bar33='1 2;3`4"5~6!7@8#9$0 1%2^3&4*5(6)7-8=9+0' '1 2;3`4"5~6!7@8#9$0 1%2^3&4*5(6)7-8=9+0=33'
|Foo_Bar33| <-- |1 2;3`4"5~6!7@8#9$0 1%2^3&4*5(6)7-8=9+0|
arg: |1 2;3`4"5~6!7@8#9$0 1%2^3&4*5(6)7-8=9+0=33|

$ ./assign.sh a=1 b=2 c d=4
|a| <-- |1|
|b| <-- |2|
arg: |c|
arg: |d=4|

กะจะฆ่าสิ่งที่ผิดถ้าคุณไม่ได้ทำลายห่วงที่คนแรกที่ไม่ใช่ x = พารามิเตอร์ Y
frostschutz

@frostschutz จุดดีแก้ไข
lcd047

เป็นงานที่ดีที่ทำให้เป็นเรื่องทั่วไป ฉันคิดว่ามันง่ายขึ้นเล็กน้อย
PSkocik

คุณมีโอกาสได้ดูการแก้ไขของฉันหรือไม่?
PSkocik

โปรดดูที่การแก้ไขของฉัน นั่นคือวิธีที่ฉันชอบ (+ อาจจะshiftแทนที่จะเป็นshift 1) ขอบคุณเป็นอย่างอื่น!
PSkocik

0

ก่อนหน้านี้ฉันนั่งaliasทำงานประเภทนี้ ต่อไปนี้เป็นคำตอบของฉัน:


บางครั้งมันอาจเป็นไปได้ที่จะแยกการประเมินและการดำเนินการของคำสั่งดังกล่าวแม้ว่า ตัวอย่างเช่นaliasสามารถใช้ประเมินค่าคำสั่งล่วงหน้าได้ ในตัวอย่างต่อไปนี้คำจำกัดความของตัวแปรจะถูกบันทึกไปยังนามแฝงที่สามารถประกาศได้สำเร็จหาก$varตัวแปรที่ประเมินอยู่นั้นไม่มีไบต์ที่ไม่ตรงกับตัวอักษรและตัวเลข ASCII หรือ _

LC_OLD=$LC_ALL LC_ALL=C
for var do    val=${var#*=} var=${var%%=*}
    alias  "${var##*[!_A-Z0-9a-z]*}=_$var=\$val" &&
    eval   "${var##[0-9]*}" && unalias "$var"
done;       LC_ALL=$LC_OLD

evalใช้ที่นี่เพื่อจัดการการเรียกใช้ใหม่aliasจากบริบท varname ที่ยกมา - ไม่ใช่สำหรับการกำหนดอย่างแน่นอน และevalถูกเรียกเพียงแค่ถ้าaliasคำจำกัดความก่อนหน้านี้ประสบความสำเร็จและในขณะที่ฉันรู้ว่าการใช้งานที่แตกต่างกันจำนวนมากจะยอมรับค่าต่าง ๆ มากมายสำหรับชื่อนามแฝงฉันยังไม่พบเชลล์ที่จะยอมรับหนึ่งที่ว่างเปล่าโดยสมบูรณ์ .

อย่างไรก็ตามคำจำกัดความภายในนามแฝงนั้นมีไว้สำหรับ_$varและนี่คือเพื่อให้แน่ใจว่าไม่มีการเขียนค่าสภาพแวดล้อมที่สำคัญ ฉันไม่รู้ค่าของสภาพแวดล้อมที่สำคัญใด ๆ ที่เริ่มต้นด้วย _ และมันมักจะเป็นทางออกที่ปลอดภัยสำหรับการประกาศแบบกึ่งส่วนตัว

อย่างไรก็ตามหากคำจำกัดความของนามแฝงประสบความสำเร็จก็จะประกาศนามแฝงที่ตั้งชื่อตาม$varค่าของ และevalจะเรียกว่าaliasหากไม่ได้ขึ้นต้นด้วยตัวเลข - evalเท่านั้นจะได้รับอาร์กิวเมนต์เป็นโมฆะเท่านั้น ดังนั้นหากตรงตามเงื่อนไขทั้งสองevalเรียกaliasและนิยามตัวแปรที่บันทึกไว้ในaliasนั้นทำหลังจากที่นามแฝงใหม่จะถูกลบออกจากตารางแฮชทันที


ยังมีประโยชน์เกี่ยวกับaliasในบริบทนี้คือคุณสามารถพิมพ์งานของคุณ aliasจะพิมพ์คำสั่งsafe-for-shell-rexecution ที่เสนอราคาซ้ำสองครั้งเมื่อถูกถาม

sh -c "IFS=\'
    alias q=\"\$*\" q" -- \
    some args which alias \
    will print back at us

เอาท์พุท

q='some'"'"'args'"'"'which'"'"'alias'"'"'will'"'"'print'"'"'back'"'"'at'"'"'us'
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.