ฟังก์ชั่นทุบตีที่รับอาร์กิวเมนต์เหมือนภาษาอื่นหรือไม่?


17

ฉันมีฟังก์ชั่นทุบตีเพื่อตั้งค่า$PATHเช่นนี้ -

assign-path()
{
    str=$1
    # if the $PATH is empty, assign it directly.
    if [ -z $PATH ]; then
        PATH=$str;
    # if the $PATH does not contain the substring, append it with ':'.
    elif [[ $PATH != *$str* ]]; then
        PATH=$PATH:$str;
    fi
}

แต่ปัญหาคือฉันต้องเขียนฟังก์ชั่นที่แตกต่างกันสำหรับตัวแปรที่แตกต่างกัน (ตัวอย่างเช่นฟังก์ชั่นอื่นสำหรับ$CLASSPATHชอบassign-classpath()ฯลฯ ) ฉันไม่สามารถหาวิธีส่งอาร์กิวเมนต์ไปยังฟังก์ชัน bash เพื่อให้สามารถเข้าถึงได้โดยการอ้างอิง

มันจะดีกว่าถ้าฉันมีบางอย่างที่เหมือน -

assign( bigstr, substr )
{
    if [ -z bigstr ]; then
        bigstr=substr;
    elif [[ bigstr != *str* ]]; then
        bigstr=bigstr:substr;
    fi
}

ความคิดใด ๆ วิธีการบรรลุสิ่งดังกล่าวข้างต้นในทุบตี?


"ภาษาอื่น ๆ " คืออะไร?
choroba

ดีฉันพยายามที่จะหมายถึงถ้าทุบตีช่วยให้ "ผ่านการอ้างอิง" ชอบใน c / java ฯลฯ
ramgorur

1
assign-path /abcจะไม่ต่อท้าย/abcไปยังเส้นทางถ้า $ PATH มีอยู่แล้ว/abc/def, /abcd, /def/abcฯลฯ โดยเฉพาะอย่างยิ่งคุณไม่สามารถเพิ่มถ้าเส้นทางที่มีอยู่แล้ว/bin /usr/bin
miracle173

@ miracle173 - นี้เป็นจริงสิ่งที่คุณต้องทำคือการแยกและการทดสอบการปฏิเสธกับข้อโต้แย้งของคุณเช่น:$PATH ในคำตอบของฉันฉันจะทำแบบนี้กับเป็นข้อโต้แย้งมากที่สุดเท่าที่คุณต้องการเพียงแค่ใช้ add=/bin dir=/usr/bin ; [ -z "${dir%"$add"}" ] || dir="${dir}:${add}"IFS=:
mikeserv

เกี่ยวข้องกับปัญหา (อินสแตนซ์) เฉพาะของการเพิ่มค่าให้กับรายการที่คั่นด้วยโคลอน: ฉันจะเพิ่มอย่างสะอาดได้  $PATHอย่างไร  และเพิ่มไดเรกทอรีไปยัง$PATHหากยังไม่มี (ในSuper User )
สกอตต์

คำตอบ:


17

ในbashคุณสามารถใช้${!varname}เพื่อขยายตัวแปรที่อ้างอิงโดยเนื้อหาของอีก เช่น:

$ var=hello
$ foo () { echo "${!1}"; }
$ foo var
hello

จากหน้าคน:

${!prefix*}
${!prefix@}
       Names matching prefix.  Expands to the names of variables whose names
       begin with prefix, separated by the first character of the IFS special
       variable.  When @ is used  and the expansion appears within double quotes,
       each variable name expands to a separate word.

นอกจากนี้การตั้งค่าตัวแปรอ้างอิงโดยเนื้อหา (ไม่มีอันตรายจากeval), declareคุณสามารถใช้ เช่น:

$ var=target
$ declare "$var=hello"
$ echo "$target"
hello

ดังนั้นคุณสามารถเขียนฟังก์ชั่นของคุณเช่นนี้ (ระวังเพราะถ้าคุณใช้declareในฟังก์ชั่นคุณต้องให้-gหรือตัวแปรจะอยู่ในท้องที่):

shopt -s extglob

assign()
{
  target=$1
  bigstr=${!1}
  substr=$2

  if [ -z "$bigstr" ]; then
    declare -g -- "$target=$substr"
  elif [[ $bigstr != @(|*:)$substr@(|:*) ]]; then
    declare -g -- "$target=$bigstr:$substr"
  fi
}

และใช้มันเช่น:

assign PATH /path/to/binaries

โปรดทราบว่าฉันได้แก้ไขข้อผิดพลาดด้วยซึ่งหากsubstrเป็นสตริงย่อยของหนึ่งในสมาชิกโคลอนที่แยกจากกันbigstrแต่ไม่ใช่สมาชิกของตัวเองแล้วมันจะไม่ถูกเพิ่ม ตัวอย่างเช่นนี้จะช่วยให้เพิ่ม/binไปยังตัวแปรที่มีอยู่แล้วPATH /usr/binมันใช้extglobชุดเพื่อให้ตรงกับจุดเริ่มต้น / จุดสิ้นสุดของสตริงหรือลำไส้ใหญ่แล้วสิ่งอื่นใด หากไม่มีextglobตัวเลือกจะเป็น:

[[ $bigstr != $substr && $bigstr != *:$substr &&
   $bigstr != $substr:* && $bigstr != *:$substr:* ]]

-gในdeclareไม่พร้อมใช้งานใน bash รุ่นเก่ากว่ามีวิธีใดบ้างที่ฉันสามารถใช้กับการย้อนกลับได้หรือไม่
ramgorur

2
@ramgorur คุณสามารถใช้exportเพื่อวางไว้ในสภาพแวดล้อมของคุณ (เสี่ยงต่อการเขียนทับสิ่งที่สำคัญ) หรือeval(ปัญหาต่าง ๆ รวมถึงความปลอดภัยหากคุณไม่ระวัง) ถ้าใช้evalคุณควรจะ ok eval "$target=\$substr"ถ้าคุณทำมันเหมือน หากคุณลืมแต่ก็อาจจะดำเนินการคำสั่งถ้ามีช่องว่างในเนื้อหาของ\ substr
แกรม

9

ใหม่ใน bash 4.3 เป็น-nตัวเลือกสำหรับdeclare& local:

func() {
    local -n ref="$1"
    ref="hello, world"
}

var='goodbye world'
func var
echo "$var"

hello, worldพิมพ์ออกมา


ปัญหาเฉพาะกับ namerefs ใน Bash คือคุณไม่สามารถมี nameref (ในฟังก์ชั่นตัวอย่าง) อ้างอิงตัวแปร (นอกฟังก์ชั่น) ที่มีชื่อเดียวกันกับชื่อ nameref สิ่งนี้อาจได้รับการแก้ไขสำหรับรีลีส 4.5 แม้ว่า
Kusalananda

2

คุณสามารถใช้evalเพื่อตั้งค่าพารามิเตอร์ คำอธิบายของคำสั่งนี้สามารถพบได้ที่นี่ การใช้งานต่อไปนี้ของevalผิด

ไม่ถูกต้อง(){
  eval $ 1 = $ 2
}

ด้วยความเคารพต่อการประเมินผลเพิ่มเติมที่evalคุณควรใช้

กำหนด(){
  eval $ 1 = '$ 2'
}

ตรวจสอบผลลัพธ์ของการใช้ฟังก์ชั่นเหล่านี้:

$ X1 = '$ X2'
$ X2 = '$ X3'
$ X3 = 'xxx'
$ 
$ echo: $ X1:
: $ X2:
$ echo: $ X2:
: $ X3:
$ echo: $ X3:
: xxx:
$ 
$ ผิด Y $ X1
$ echo: $ Y:
: $ X3:
$ 
$ กำหนด Y $ X1
$ echo: $ Y:
: $ X2:
$ 
$ กำหนด Y "ฮัลโลโลก"
$ echo: $ Y:
: สวัสดีโลก:
$ # ต่อไปนี้อาจไม่คาดคิด
$ กำหนด Z $ Y
$ echo ": $ Z:"
: Hallo:
$ # ดังนั้นคุณต้องอ้างอิงอาร์กิวเมนต์ที่สองหากเป็นตัวแปร
$ กำหนด Z "$ Y"
$ echo ": $ Z:"
: สวัสดีโลก:

evalแต่คุณสามารถบรรลุเป้าหมายของคุณโดยการใช้งานของ ฉันชอบวิธีนี้ที่ง่ายกว่ามาก

ฟังก์ชั่นต่อไปนี้ทำให้การทดแทนในวิธีที่ถูกต้อง (ฉันหวังว่า)

ขยาย () {
  local CURRENT = $ 1
  การเพิ่มท้องถิ่น = $ 2
  ใหม่ในท้องถิ่น
  ถ้า [[-z $ หมุนเวียน]]; แล้วก็
    NEW = $ เพิ่ม
  elif [[! (($ CURRENT = $ AUGMENT) || ($ CURRENT = $ AUGMENT: *) || \
    ($ CURRENT = *: $ AUGMENT) || ($ CURRENT = *: $ AUGMENT: *))]]; แล้วก็
    NEW = $ หมุนเวียน: $ เพิ่ม
  อื่น
    NEW = $ หมุนเวียน
    Fi
  echo "$ ใหม่"
}

ตรวจสอบผลลัพธ์ต่อไปนี้

เพิ่ม / usr / bin / bin
/ usr / bin: / bin

เพิ่ม / usr / bin: / bin / bin
/ usr / bin: / bin

เพิ่ม / usr / bin: / bin: / usr / local / bin / bin
/ usr / bin: / bin: / usr / local / bin

augment / bin: / usr / bin / bin
/ bin: / usr / bin

เพิ่ม / bin / bin
/ bin


เพิ่ม / usr / bin: / bin
/ usr / bin :: / bin

เพิ่ม / usr / bin: / bin: / bin
/ usr / bin: / bin:

เพิ่ม / usr / bin: / bin: / usr / local / bin: / bin
/ usr / bin: / bin: / usr / local / bin:

augment / bin: / usr / bin: / bin
/ bin: / usr / bin:

เพิ่ม / bin: / bin
/ bin:


เพิ่ม: / bin
:: / bin


เพิ่ม "/ usr lib" "/ usr bin"
/ usr lib: / usr bin

เพิ่ม "/ usr lib: / usr bin" "/ usr bin"
/ usr lib: / usr bin

ตอนนี้คุณสามารถใช้augmentฟังก์ชันด้วยวิธีต่อไปนี้เพื่อตั้งค่าตัวแปร:

PATH = `เพิ่มเส้นทาง / bin`
CLASSPATH = `เพิ่ม CLASSPATH / bin`
LD_LIBRARY_PATH = `เพิ่ม LD_LIBRARY_PATH / usr / lib`

แม้แต่คำให้การของคุณก็ผิด ตัวอย่างเช่นนี้v='echo "OHNO!" ; var' ; l=val ; eval $v='$l' - จะสะท้อน "OHNO! ก่อนกำหนด var คุณสามารถ" $ {v ## * [; "$ IFS"]}} = '$ l' "เพื่อให้แน่ใจว่าสตริงไม่สามารถขยายไปยังสิ่งที่ จะไม่ถูกประเมินด้วย =.
mikeserv

@mikeserv ขอบคุณสำหรับความคิดเห็นของคุณ แต่ฉันคิดว่านั่นไม่ใช่ตัวอย่างที่ถูกต้อง อาร์กิวเมนต์แรกของสคริปต์กำหนดควรเป็นชื่อตัวแปรหรือตัวแปรที่มีชื่อตัวแปรที่ใช้ทางด้านซ้ายมือของ=คำสั่งมอบหมาย คุณสามารถยืนยันได้ว่าใบปลิวของฉันไม่ตรวจสอบการโต้แย้ง นั่นเป็นความจริง. ฉันไม่ได้ตรวจสอบว่ามีข้อโต้แย้งใด ๆ หรือจำนวนข้อโต้แย้งที่ถูกต้อง แต่นี่เป็นความตั้งใจ OP สามารถเพิ่มเช็คดังกล่าวหากเขาต้องการ
miracle173

@mikeserv: ฉันคิดว่าข้อเสนอของคุณในการแปลงอาร์กิวเมนต์แรกอย่างเงียบ ๆ เป็นชื่อตัวแปรที่ถูกต้องไม่ใช่ความคิดที่ดี: 1) ตัวแปรบางตัวถูกตั้งค่า / เขียนทับซึ่งผู้ใช้ไม่ได้ตั้งใจ 2) ข้อผิดพลาดถูกซ่อนจากผู้ใช้ฟังก์ชั่น นั่นไม่ใช่ความคิดที่ดี ควรเพียงแค่เกิดข้อผิดพลาดหากเกิดขึ้น
miracle173

@mikeserv: เป็นที่น่าสนใจเมื่อต้องการใช้ตัวแปรของคุณv(ดีกว่าค่าของมัน) เป็นอาร์กิวเมนต์ที่สองของฟังก์ชั่นกำหนด ดังนั้นมูลค่าของมันควรอยู่ทางด้านขวามือของการโอนสิทธิ์ มันเป็น necessaary assignจะพูดอาร์กิวเมนต์ของฟังก์ชัน ฉันเพิ่มความละเอียดอ่อนนี้ในการโพสต์ของฉัน
miracle173

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

2

ด้วยเทคนิคเล็กน้อยคุณสามารถส่งพารามิเตอร์ที่มีชื่อไปยังฟังก์ชั่นพร้อมกับอาร์เรย์ (ทดสอบใน bash 3 และ 4)

วิธีที่ฉันพัฒนาให้คุณเข้าถึงพารามิเตอร์ที่ส่งผ่านไปยังฟังก์ชันดังนี้:

testPassingParams() {

    @var hello
    l=4 @array anArrayWithFourElements
    l=2 @array anotherArrayWithTwo
    @var anotherSingle
    @reference table   # references only work in bash >=4.3
    @params anArrayOfVariedSize

    test "$hello" = "$1" && echo correct
    #
    test "${anArrayWithFourElements[0]}" = "$2" && echo correct
    test "${anArrayWithFourElements[1]}" = "$3" && echo correct
    test "${anArrayWithFourElements[2]}" = "$4" && echo correct
    # etc...
    #
    test "${anotherArrayWithTwo[0]}" = "$6" && echo correct
    test "${anotherArrayWithTwo[1]}" = "$7" && echo correct
    #
    test "$anotherSingle" = "$8" && echo correct
    #
    test "${table[test]}" = "works"
    table[inside]="adding a new value"
    #
    # I'm using * just in this example:
    test "${anArrayOfVariedSize[*]}" = "${*:10}" && echo correct
}

fourElements=( a1 a2 "a3 with spaces" a4 )
twoElements=( b1 b2 )
declare -A assocArray
assocArray[test]="works"

testPassingParams "first" "${fourElements[@]}" "${twoElements[@]}" "single with spaces" assocArray "and more... " "even more..."

test "${assocArray[inside]}" = "adding a new value"

กล่าวอีกนัยหนึ่งไม่เพียง แต่คุณสามารถเรียกพารามิเตอร์ของคุณตามชื่อของพวกเขา (ซึ่งประกอบไปด้วยแกนกลางที่อ่านได้มากขึ้น) คุณสามารถส่งผ่านอาร์เรย์ได้ (และการอ้างอิงถึงตัวแปร - คุณลักษณะนี้ใช้ได้เฉพาะในทุบตี 4.3)! นอกจากนี้ตัวแปรที่แมปนั้นอยู่ในขอบเขตของท้องถิ่นเช่นเดียวกับ $ 1 (และอื่น ๆ )

รหัสที่ทำให้งานนี้ค่อนข้างเบาและใช้ได้ทั้งใน bash 3 และ bash 4 (นี่เป็นรุ่นเดียวที่ฉันทดสอบด้วย) หากคุณสนใจเทคนิคเพิ่มเติมเช่นนี้ที่พัฒนาด้วย bash ที่ดีกว่าและง่ายกว่าคุณสามารถดูBash Infinity Frameworkของฉันได้รหัสด้านล่างนี้ได้รับการพัฒนาเพื่อจุดประสงค์นั้น

Function.AssignParamLocally() {
    local commandWithArgs=( $1 )
    local command="${commandWithArgs[0]}"

    shift

    if [[ "$command" == "trap" || "$command" == "l="* || "$command" == "_type="* ]]
    then
        paramNo+=-1
        return 0
    fi

    if [[ "$command" != "local" ]]
    then
        assignNormalCodeStarted=true
    fi

    local varDeclaration="${commandWithArgs[1]}"
    if [[ $varDeclaration == '-n' ]]
    then
        varDeclaration="${commandWithArgs[2]}"
    fi
    local varName="${varDeclaration%%=*}"

    # var value is only important if making an object later on from it
    local varValue="${varDeclaration#*=}"

    if [[ ! -z $assignVarType ]]
    then
        local previousParamNo=$(expr $paramNo - 1)

        if [[ "$assignVarType" == "array" ]]
        then
            # passing array:
            execute="$assignVarName=( \"\${@:$previousParamNo:$assignArrLength}\" )"
            eval "$execute"
            paramNo+=$(expr $assignArrLength - 1)

            unset assignArrLength
        elif [[ "$assignVarType" == "params" ]]
        then
            execute="$assignVarName=( \"\${@:$previousParamNo}\" )"
            eval "$execute"
        elif [[ "$assignVarType" == "reference" ]]
        then
            execute="$assignVarName=\"\$$previousParamNo\""
            eval "$execute"
        elif [[ ! -z "${!previousParamNo}" ]]
        then
            execute="$assignVarName=\"\$$previousParamNo\""
            eval "$execute"
        fi
    fi

    assignVarType="$__capture_type"
    assignVarName="$varName"
    assignArrLength="$__capture_arrLength"
}

Function.CaptureParams() {
    __capture_type="$_type"
    __capture_arrLength="$l"
}

alias @trapAssign='Function.CaptureParams; trap "declare -i \"paramNo+=1\"; Function.AssignParamLocally \"\$BASH_COMMAND\" \"\$@\"; [[ \$assignNormalCodeStarted = true ]] && trap - DEBUG && unset assignVarType && unset assignVarName && unset assignNormalCodeStarted && unset paramNo" DEBUG; '
alias @param='@trapAssign local'
alias @reference='_type=reference @trapAssign local -n'
alias @var='_type=var @param'
alias @params='_type=params @param'
alias @array='_type=array @param'

1
assign () 
{ 
    if [ -z ${!1} ]; then
        eval $1=$2
    else
        if [[ ${!1} != *$2* ]]; then
            eval $1=${!1}:$2
        fi
    fi
}

$ echo =$x=
==
$ assign x y
$ echo =$x=
=y=
$ assign x y
$ echo =$x=
=y=
$ assign x z
$ echo =$x=
=y:z=

พอดีนี้ไหม


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

1
การใช้งานของคุณevalมีความอ่อนไหวต่อการดำเนินการคำสั่งโดยอำเภอใจ
Chris Down

คุณมีความคิดที่จะทำให้evalเส้นzhe ปลอดภัยยิ่งขึ้น? โดยปกติเมื่อฉันคิดว่าฉันต้องการการประเมินฉันตัดสินใจที่จะไม่ใช้ * sh และเปลี่ยนไปใช้ภาษาอื่นแทน ในทางกลับกันการใช้สิ่งนี้ใน Scrips เพื่อผนวกรายการไปยังตัวแปรที่คล้ายกับ PATH บางอย่างมันจะทำงานด้วยค่าคงที่สตริงและไม่ใช่อินพุตของผู้ใช้ ...

1
คุณสามารถทำได้evalอย่างปลอดภัย - แต่ต้องใช้ความคิดอย่างมาก หากคุณเพียงแค่พยายามอ้างอิงพารามิเตอร์คุณต้องการทำสิ่งนี้: eval "$1=\"\$2\""วิธีการeval'sส่งผ่านครั้งแรกจะประเมินเพียง $ 1 และอันดับที่สองจะมีค่า = "$ 2" แต่คุณต้องทำอย่างอื่น - นี่ไม่จำเป็นที่นี่
mikeserv

ที่จริงแล้วความเห็นด้านบนของฉันก็ผิดเช่นกัน คุณต้องทำ"${1##*[;"$IFS"]}=\"\$2\""และถึงแม้จะไม่มีการรับประกันก็ตาม eval "$(set -- $1 ; shift $(($#-1)) ; echo $1)=\"\$2\""หรือ มันไม่ง่าย.
mikeserv

1

อาร์กิวเมนต์ที่กำหนดชื่อไม่ใช่วิธีการออกแบบไวยากรณ์ของ Bash Bash ได้รับการออกแบบมาเพื่อการปรับปรุงซ้ำในเชลล์เป้าหมาย ดังนั้นจึงจำเป็นต้องให้แน่ใจว่าบางสิ่งทำงานระหว่างสองเชลล์มากที่สุด ดังนั้นจึงไม่ได้หมายความว่าจะง่ายต่อการเขียนสคริปต์โดยรวม แต่ก็ควรจะดีกว่า Bourne ในขณะเดียวกันก็มั่นใจได้ว่าหากคุณใช้สคริปต์จากสภาพแวดล้อมของ Bourne มาbashเป็นเรื่องง่ายที่สุด มันไม่ได้เป็นเรื่องเล็กน้อยเพราะกระสุนจำนวนมากยังคงใช้บอร์นเป็นมาตรฐานตามความเป็นจริง เนื่องจากผู้คนเขียนสคริปต์เพื่อให้สามารถใช้งานร่วมกับ Bourne ได้ (สำหรับการพกพานี้) ความต้องการยังคงมีผลบังคับใช้และไม่น่าจะเปลี่ยนแปลงได้

คุณน่าจะดีกว่าถ้าดูเชลล์สคริปต์อื่น ๆ (เช่นpythonหรือบางอย่าง) โดยสิ้นเชิงถ้าทำได้จริง หากคุณพบข้อ จำกัด ของภาษาคุณต้องเริ่มใช้ภาษาใหม่


บางทีในช่วงต้นbashชีวิตอาจเป็นเรื่องจริง แต่ตอนนี้มีบทบัญญัติเฉพาะ ตัวแปรอ้างอิงแบบเต็มขณะนี้อยู่ในbash 4.3- ดูคำตอบของ derobert
แกรม

และถ้าคุณดูที่นี่คุณจะเห็นว่าคุณสามารถทำสิ่งนี้ได้อย่างง่ายดายแม้กับรหัสพกพา POSIX: unix.stackexchange.com/a/120531/52934
mikeserv

1

ด้วยshไวยากรณ์มาตรฐาน(สามารถใช้งานbashได้และไม่เพียง แต่อยู่ในbash) คุณสามารถทำได้:

assign() {
  eval '
    case :${'"$1"'}: in
      (::) '"$1"'=$2;;   # was empty, copy
      (*:"$2":*) ;;      # already there, do nothing
      (*) '"$1"'=$1:$2;; # otherwise, append with a :
    esac'
}

ชอบสำหรับการแก้ปัญหาโดยใช้bash's declareก็ปลอดภัยตราบใดที่$1มีชื่อตัวแปรที่ถูกต้อง


0

เกี่ยวกับชื่อที่ตั้ง:

สิ่งนี้ทำได้ง่ายมากและbashไม่จำเป็นเลย - นี่เป็นพฤติกรรมพื้นฐานของ POSIX ที่ระบุการมอบหมายผ่านการขยายพารามิเตอร์:

: ${PATH:=this is only assigned to \$PATH if \$PATH is null or unset}

หากต้องการสาธิตในหลอดเลือดดำที่คล้ายกับ @Graeme แต่ใช้วิธีพกพา:

_fn() { echo "$1 ${2:-"$1"} $str" ; }

% str= ; _fn "${str:=hello}"
> hello hello hello

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

วิธีการแก้:

สำหรับปัญหาเฉพาะของคุณฉันไม่เชื่อว่าการโต้แย้งที่มีชื่อเป็นสิ่งจำเป็น แต่ก็เป็นไปได้อย่างแน่นอน ใช้$IFSแทน:

assign() { oFS=$IFS ; IFS=: ; add=$* 
    set -- $PATH ; for p in $add ; do { 
        for d ; do [ -z "${d%"$p"}" ] && break 
        done ; } || set -- $* $p ; done
    PATH= ; echo "${PATH:="$*"}" ; IFS=$oFS
}

นี่คือสิ่งที่ฉันได้รับเมื่อฉันเรียกใช้:

% PATH=/usr/bin:/usr/yes/bin
% assign \
    /usr/bin \
    /usr/yes/bin \
    /usr/nope/bin \
    /usr/bin \
    /nope/usr/bin \
    /usr/nope/bin

> /usr/bin:/usr/yes/bin:/usr/nope/bin:/nope/usr/bin

% echo "$PATH"
> /usr/bin:/usr/yes/bin:/usr/nope/bin:/nope/usr/bin

% dir="/some crazy/dir"
% p=`assign /usr/bin /usr/bin/new "$dir"`
% echo "$p" ; echo "$PATH"
> /usr/bin:/usr/yes/bin:/usr/nope/bin:/nope/usr/bin:/some crazy/dir:/usr/bin/new
> /usr/bin:/usr/yes/bin:/usr/nope/bin:/nope/usr/bin:/some crazy/dir:/usr/bin/new

แจ้งให้ทราบล่วงหน้าว่ามันได้เพิ่มข้อโต้แย้งที่ยังไม่เคยมี$PATHมาก่อนหรือ? หรือแม้แต่การโต้แย้งมากกว่าหนึ่งเรื่อง? $IFSมีประโยชน์


สวัสดีฉันล้มเหลวในการติดตามดังนั้นคุณช่วยอธิบายเพิ่มเติมได้ไหม? ขอบคุณ
ramgorur

ฉันได้ทำไปแล้ว ... โปรดรอสักครู่อีกสักครู่ ...
mikeserv

@ramgorur ดีกว่าไหม? ขออภัย แต่ชีวิตจริงบุกเข้ามาและฉันใช้เวลานานกว่าที่ฉันคาดไว้ในการเขียนบทความให้เสร็จ
mikeserv

เหมือนกันที่นี่เช่นกันยอมจำนนต่อชีวิตจริง ดูเหมือนว่ามีวิธีที่แตกต่างกันมากมายในการเขียนโค้ดสิ่งนี้ขอผมให้บางครั้งผมใช้วิธีที่ดีที่สุด
ramgorur

@ramgorur - แน่นอนแค่ต้องการให้แน่ใจว่าฉันไม่ได้ปล่อยให้คุณแขวน เกี่ยวกับสิ่งนี้ - คุณเลือกสิ่งที่คุณต้องการผู้ชาย ฉันจะบอกว่าไม่มีคำตอบอื่น ๆ ที่ฉันสามารถเห็นข้อเสนอเป็นกระชับ, แบบพกพาหรือวิธีแก้ปัญหาที่แข็งแกร่งเช่นเดียวกับassignที่นี่ หากคุณมีคำถามเกี่ยวกับวิธีการทำงานฉันยินดีที่จะตอบ และโดยวิธีการถ้าคุณต้องการอาร์กิวเมนต์ที่มีชื่อจริงๆคุณอาจต้องการดูคำตอบอื่น ๆ ของฉันที่ฉันแสดงให้เห็นว่าการประกาศฟังก์ชั่นการตั้งชื่อตามข้อโต้แย้งของฟังก์ชั่นอื่น: unix.stackexchange.com/a/120531/52934
mikeserv

-2

ฉันไม่พบอะไรเช่นทับทิมหลาม ฯลฯ แต่นี่ทำให้ฉันรู้สึกใกล้ชิดมากขึ้น

foo() {
  BAR="$1"; BAZ="$2"; QUUX="$3"; CORGE="$4"
  ...
}

ในความเห็นของฉันการอ่านดีกว่ามี 4 บรรทัดมากเกินไปสำหรับการประกาศชื่อพารามิเตอร์ของคุณ มองใกล้กับภาษาที่ทันสมัยเช่นกัน


(1) คำถามเกี่ยวกับฟังก์ชั่นของเชลล์ คำตอบของคุณนำเสนอฟังก์ชันเชลล์โครงกระดูก ยิ่งกว่านั้นคำตอบของคุณไม่มีส่วนเกี่ยวข้องกับคำถาม (2) คุณคิดว่ามันเพิ่มความสามารถในการอ่านเพื่อแยกบรรทัดและต่อกันเป็นหนึ่งบรรทัดโดยมีเครื่องหมายอัฒภาคเป็นตัวคั่นหรือไม่ ฉันเชื่อว่าคุณได้รับมันไปข้างหลัง; ที่สไตล์แบบบรรทัดเดียวของคุณ  อ่านได้น้อยกว่าสไตล์แบบหลายบรรทัด
สกอตต์

หากการลดส่วนที่ไม่จำเป็นเป็นจุดทำไมราคาและอัฒภาค? a=$1 b=$2 ...ทำงานได้เช่นกัน
ilkkachu
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.