ฉันจะเพิ่ม $ PATH ให้สะอาดได้อย่างไร?


31

ฉันต้องการวิธีเพิ่มสิ่งต่าง ๆ ใน $ PATH ทั้งระบบหรือสำหรับผู้ใช้รายบุคคลโดยไม่ต้องเพิ่มพา ธ เดียวกันหลาย ๆ ครั้ง

เหตุผลหนึ่งที่จะต้องการที่จะทำเช่นนี้คือเพื่อให้ข้อมูลเพิ่มเติมสามารถทำใน.bashrcซึ่งไม่จำเป็นต้องเข้าสู่ระบบและยังเป็นประโยชน์เพิ่มเติมเกี่ยวกับระบบที่ใช้งาน (เช่น) ซึ่งไม่เคยโทรlightdm.profile

ฉันรู้คำถามจัดการกับวิธีการที่ซ้ำกันทำความสะอาดจาก $ PATH แต่ฉันไม่ต้องการที่จะลบข้อมูลที่ซ้ำกัน ฉันต้องการวิธีเพิ่มเส้นทางหากไม่มีอยู่แล้วเท่านั้น


เป็นไปได้ที่ซ้ำกันของเก็บซ้ำจาก $ PATH ในแหล่งที่มา
Ciro Santilli

goldi ฉันไม่รู้ว่าทำไม แต่ฉันเห็นความคิดเห็นแรกของคุณแม้จะว่างเปล่า แต่ใช่คำนำหน้าชื่อยังใช้งานได้ไม่ต้องกังวล! การปิดทางอื่นก็ทำได้เช่นกัน
Ciro Santilli 新疆改造中心法轮功六四事件

ตกลงตราบใดที่คุณได้รับข้อความของฉัน บางครั้งการกลับรายการเช่นนี้ทำให้เกิดความสับสนเล็กน้อยฉันคิดว่าเราจะเห็นว่าเกิดอะไรขึ้น
goldilocks

คำตอบ:


35

สมมติว่าเส้นทางใหม่ที่เราต้องการเพิ่มคือ:

new=/opt/bin

จากนั้นใช้เปลือก POSIX ใด ๆ เราสามารถทดสอบเพื่อดูว่าnewมีอยู่แล้วในเส้นทางและเพิ่มเข้าไปหากไม่มี:

case ":${PATH:=$new}:" in
    *:"$new":*)  ;;
    *) PATH="$new:$PATH"  ;;
esac

หมายเหตุการใช้โคลอน โดยไม่ต้องทวิภาคที่เราอาจจะคิดว่าการพูดที่มีอยู่แล้วในเส้นทางเพราะรูปแบบการจับคู่ในnew=/bin /usr/binในขณะที่ PATH ปกติแล้วจะมีองค์ประกอบมากมายกรณีพิเศษที่มีค่าเป็นศูนย์และอีกหนึ่งองค์ประกอบใน PATH จะได้รับการจัดการด้วย กรณีของ PATH เริ่มต้นที่ไม่มีอิลิเมนต์ (กำลังว่างเปล่า) ถูกจัดการโดยการใช้${PATH:=$new}ซึ่งกำหนดให้PATHกับ$newถ้ามันว่างเปล่า การตั้งค่าเริ่มต้นสำหรับพารามิเตอร์ด้วยวิธีนี้เป็นคุณสมบัติของเชลล์ POSIX ทั้งหมด: ดูหัวข้อ 2.6.2 ของเอกสาร POSIX )

ฟังก์ชั่น callable

เพื่อความสะดวกรหัสข้างต้นสามารถใส่ลงในฟังก์ชั่น ฟังก์ชั่นนี้สามารถกำหนดได้ที่บรรทัดคำสั่งหรือเพื่อให้สามารถใช้งานได้อย่างถาวรใส่ลงในสคริปต์การเริ่มต้นของเชลล์ (สำหรับผู้ใช้ทุบตีนั่นคือ~/.bashrc):

pupdate() { case ":${PATH:=$1}:" in *:"$1":*) ;; *) PATH="$1:$PATH" ;; esac; }

เมื่อต้องการใช้ฟังก์ชันการอัพเดตพา ธ นี้เพื่อเพิ่มไดเร็กทอรีไปยัง PATH ปัจจุบัน:

pupdate /new/path

@hammar ตกลง ฉันได้เพิ่มเคสสำหรับสิ่งนั้น
John1024

1
คุณสามารถบันทึกความแตกต่างของ 2 กรณี - cf. unix.stackexchange.com/a/40973/1131
maxschlepzig

3
ถ้าPATHเป็นที่ว่างเปล่านี้จะเพิ่มรายการที่ว่างเปล่า (เช่นไดเรกทอรีปัจจุบัน) PATHเพื่อ ฉันคิดว่าคุณต้องการเคสอื่น
CB Bailey

2
@CharlesBailey ไม่ใช่อีกcaseแล้ว case "${PATH:=$new}"เพียงแค่ทำ ดูคำตอบของฉันเองสำหรับทางเลือกที่คล้ายกัน
mikeserv

1
@ mc0e ฉันเพิ่มตัวอย่างของวิธีการใช้ฟังก์ชั่นเปลือกเพื่อซ่อน "เสียงรบกวนสาย"
John1024

9

สร้างไฟล์ที่/etc/profile.dเรียกเช่นmypath.sh(หรือสิ่งที่คุณต้องการ) หากคุณกำลังใช้ lightdm ตรวจสอบให้แน่ใจว่ามันใช้งานได้หรืออย่างอื่นใช้/etc/bashrcหรือไฟล์ที่มาจากเดียวกัน เพิ่มไปยังฟังก์ชั่นต่อไปนี้:

checkPath () {
        case ":$PATH:" in
                *":$1:"*) return 1
                        ;;
        esac
        return 0;
}

# Prepend to $PATH
prependToPath () {
        for a; do
                checkPath $a
                if [ $? -eq 0 ]; then
                        PATH=$a:$PATH
                fi
        done
        export PATH
}

# Append to $PATH
appendToPath () {
        for a; do
                checkPath $a
                if [ $? -eq 0 ]; then
                        PATH=$PATH:$a
                fi
        done
        export PATH
}

สิ่งที่จุดเริ่มต้นของ (เตรียมไว้) $ PATH มีความสำคัญมากกว่าสิ่งต่อไปนี้และตรงกันข้ามสิ่งต่าง ๆ ในตอนท้าย (ต่อท้าย) จะถูกแทนที่โดยสิ่งที่มาก่อน ซึ่งหมายความว่าถ้า $ PATH ของคุณเป็น/usr/local/bin:/usr/binและมีการปฏิบัติการgotchaในทั้งสองไดเรกทอรีหนึ่งใน/usr/local/binจะถูกใช้โดยค่าเริ่มต้น

ตอนนี้คุณสามารถ - ในไฟล์เดียวกันนี้ในไฟล์ shell อีกไฟล์หรือจาก commandline - ใช้:

appendToPath /some/path /another/path
prependToPath /some/path /yet/another/path

หากสิ่งนี้อยู่ใน a .bashrcมันจะป้องกันไม่ให้ค่าปรากฏมากกว่าหนึ่งครั้งเมื่อคุณเริ่มเชลล์ใหม่ มีข้อ จำกัด ในการที่ถ้าคุณต้องการผนวกบางสิ่งที่ถูกผนวกไว้ (เช่นย้ายเส้นทางภายใน $ PATH) หรือในทางกลับกันคุณจะต้องทำด้วยตัวเอง


แยก$PATHกับท้ายที่สุดก็คือความยืดหยุ่นมากกว่าIFS=: case
mikeserv

@mikeserv ไม่ต้องสงสัยเลย นี่เป็นแฮ็คชนิดหนึ่งที่ใช้สำหรับcaseIMO ฉันคิดว่าawkสามารถใช้งานได้ดีที่นี่เช่นกัน
goldilocks

นั่นเป็นจุดที่ดี และที่ผมคิดว่าโดยตรงสามารถกำหนดgawk $PATH
mikeserv

5

คุณสามารถทำได้ด้วยวิธีนี้:

echo $PATH | grep /my/bin >/dev/null || PATH=$PATH:/my/bin

หมายเหตุ: ถ้าคุณสร้าง PATH จากตัวแปรอื่น ๆ ให้ตรวจสอบว่ามันไม่ได้ว่างเปล่าเพราะเชลล์หลาย ๆ ตัวแปลว่า "" like " .


+1 ตามหน้า man -qต้องการ POSIX สำหรับ grep แต่ฉันไม่รู้ว่านั่นหมายความว่ายังมี greps (ไม่ใช่ POSIX) ที่ยังไม่มี
goldilocks

1
โปรดทราบว่ารูปแบบ grep กว้างเกินไป ลองใช้ egrep -q "(^ |:) / my / bin (: | \ $)" แทน grep / my / bin> / dev / null ด้วยการแก้ไขดังกล่าวโซลูชันของคุณถูกต้องและฉันคิดว่านี่เป็นวิธีแก้ปัญหาที่อ่านได้มากกว่าคำตอบที่ต้องการในปัจจุบันจาก @ john1024 โปรดทราบว่าฉันใช้เครื่องหมายคำพูดคู่เพื่อให้คุณใช้การแทนที่ตัวแปรแทน/my/bin
mc0e

5

ส่วนที่สำคัญของรหัสคือการตรวจสอบว่าPATHมีเส้นทางเฉพาะหรือไม่:

printf '%s' ":${PATH}:" | grep -Fq ":${my_path}:"

นั่นคือตรวจสอบให้แน่ใจว่าแต่ละพา ธ ในPATHนั้นคั่นด้วยทั้งสองข้างโดยPATHตัวคั่น ( :) จากนั้นตรวจสอบ ( -q) ว่าสตริงตัวอักษร ( -F) ประกอบด้วยPATHตัวคั่นเส้นทางของคุณและPATHตัวคั่นอื่นอยู่ในนั้นหรือไม่ หากไม่เป็นเช่นนั้นคุณสามารถเพิ่มเส้นทางได้อย่างปลอดภัย:

if ! printf '%s' ":${PATH-}:" | grep -Fq ":${my_path-}:"
then
    PATH="${PATH-}:${my_path-}"
fi

สิ่งนี้ควรเข้ากันได้กับ POSIXและควรทำงานกับพา ธ ใด ๆ ที่ไม่มีอักขระขึ้นบรรทัดใหม่ มันซับซ้อนกว่าถ้าคุณต้องการให้มันทำงานกับพา ธ ที่มีบรรทัดใหม่ในขณะที่เข้ากันได้กับ POSIX แต่ถ้าคุณมีอันไหนgrepที่รองรับ-zคุณสามารถใช้มันได้


4

ฉันได้ใช้ฟังก์ชั่นเล็ก ๆ นี้กับฉันเป็น~/.profileไฟล์ต่าง ๆมาหลายปีแล้ว ฉันคิดว่ามันถูกเขียนโดยดูแลระบบในห้องแล็บที่ฉันเคยทำงาน แต่ฉันไม่แน่ใจ อย่างไรก็ตามมันคล้ายกับแนวทางของ Goldilock แต่แตกต่างกันเล็กน้อย:

pathmunge () {
        if ! echo $PATH | /bin/grep -Eq "(^|:)$1($|:)" ; then
           if [ "$2" = "after" ] ; then
              PATH=$PATH:$1
           else
              PATH=$1:$PATH
           fi
        fi
}

ดังนั้นเพื่อเพิ่มไดเรกทอรีใหม่ไปที่จุดเริ่มต้นของPATH:

pathmunge /new/path

และท้ายที่สุด:

pathmunge /new/path after

มันใช้งานได้สำหรับฉัน! แต่ฉันสลับไปใช้ตรรกะเพื่อวางไว้ตามค่าเริ่มต้นและแทนที่ด้วย "ก่อน" :)
Kevin Pauli

pathmunge เป็นส่วนหนึ่งของ linux centos distribution / etc / profile ซึ่งมีพารามิเตอร์ก่อนและหลัง ฉันไม่เห็นมันใน Ubuntu ล่าสุดของฉัน 16.
Kemin Zhou

ดูเหมือนว่าจะทำงานได้ดีบน macOS 10.12 หลังจาก/bin/grep->grep
Ben Creasy

4

UPDATE:

ผมสังเกตเห็นคำตอบของคุณเองมีฟังก์ชั่นที่แยกต่างหากสำหรับแต่ละผนวกหรือ prepending $PATHไป ฉันชอบความคิด ดังนั้นฉันจึงเพิ่มการจัดการอาร์กิวเมนต์เล็กน้อย ฉันยังตั้งชื่ออย่างถูกต้อง_:

_path_assign() { oFS=$IFS ; IFS=: ; add=$* ; unset P A ; A=
    set -- ${PATH:=$1} ; for p in $add ; do {
        [ -z "${p%-[AP]}" ] && { unset P A
                eval ${p#-}= ; continue ; }
        for d ; do [ -z "${d%"$p"}" ] && break
        done ; } || set -- ${P+$p} $* ${A+$p}
        done ; export PATH="$*" ; IFS=$oFS
}

% PATH=/usr/bin:/usr/yes/bin
% _path_assign \
    /usr/bin \
    /usr/yes/bin \
    /usr/bin/nope \
    -P \
    /usr/nope/bin \
    /usr/bin \
    -A \
    /nope/usr/bin \
    /usr/nope/bin

% echo $PATH

เอาท์พุท:

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

โดยค่าเริ่มต้นมันจะเป็น-Append $PATHแต่คุณสามารถเปลี่ยนพฤติกรรมนี้เพื่อส่ง-Pคืนได้โดยเพิ่ม-Pที่ใดก็ได้ในรายการอาร์กิวเมนต์ของคุณ คุณสามารถเปลี่ยนกลับเป็น-Appending ได้โดยส่ง-Aอีกครั้ง

ปลอดภัย EVAL

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

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

วิธีนี้จะยอมรับข้อโต้แย้งได้มากเท่าที่คุณให้ไว้และเพิ่มแต่ละข้อเป็น$PATHครั้งเดียวเท่านั้นหากยังไม่ได้เข้า$PATHร่วม มันใช้ประโยชน์จากเปลือกสคริปต์ POSIX แบบพกพาอย่างเต็มที่เท่านั้นอาศัยในตัวเชลล์และรวดเร็วมาก

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

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

@ TAFKA'goldilocks 'ดูการอัปเดตที่นี่ - คุณเป็นแรงบันดาลใจให้ฉัน
mikeserv

+1 ด้วยความอยากรู้อยากเห็น (อาจเป็นคำถามและคำตอบที่แยกต่างหาก) แนวคิดที่การ_ทำงานของเชลล์นำหน้าทำให้การ "ตั้งชื่ออย่างถูกต้อง" มาจากไหน ในภาษาอื่นมันมักจะบ่งบอกถึงฟังก์ชั่นทั่วโลกภายใน (นั่นคือคนที่จะต้องเป็นสากล แต่ไม่ได้มีวัตถุประสงค์ที่จะใช้ภายนอกเป็นส่วนหนึ่งของ API) ชื่อของฉันไม่ได้เป็นตัวเลือกที่ดี แต่สำหรับฉันที่ใช้เพียงอย่างเดียวดูเหมือนจะ_ไม่ได้ช่วยแก้ปัญหาการชนกันเลย - มันจะเป็นการดีกว่าถ้าคุณใช้เนมสเปซจริงเช่น mikeserv_path_assign().
goldilocks

@ TAFKA'goldilocks '- มันจะดีกว่าถ้าจะเจาะจงให้มากกว่านี้อีกต่อไป แต่ยิ่งชื่อยาวเท่าไหร่ก็ยิ่งใช้งานได้ง่ายขึ้นเท่านั้น แต่ถ้าคุณมีไบนารีที่เรียกใช้งานได้ที่เหมาะสมนำหน้าด้วย_คุณจะต้องสลับผู้จัดการแพคเกจ ไม่ว่าในกรณีใดก็ตามสิ่งนี้เป็นเพียง aa "global, internal, function" - เป็น global สำหรับเชลล์ทุกตัวที่ถูกเรียกจาก shell ที่มันถูกประกาศและมันเป็นเพียงเล็กน้อยของสคริปต์ภาษาที่แปลความหมายไว้ในหน่วยความจำของ interpreter . unix.stackexchange.com/questions/120528/…
mikeserv

คุณไม่สามารถunset a(หรือเทียบเท่า) ที่ส่วนท้ายของโปรไฟล์ได้หรือไม่
sourcejedi

0

ดูเถิด! 12-line ที่แข็งแกร่งในอุตสาหกรรม...ฟังก์ชั่นเปลือก bash- และ zsh-portable ในทางเทคนิคที่รักสคริปต์ของคุณ~/.bashrcหรือ~/.zshrcสคริปต์เริ่มต้นที่เลือกโดยเฉพาะ:

# void +path.append(str dirname, ...)
#
# Append each passed existing directory to the current user's ${PATH} in a
# safe manner silently ignoring:
#
# * Relative directories (i.e., *NOT* prefixed by the directory separator).
# * Duplicate directories (i.e., already listed in the current ${PATH}).
# * Nonextant directories.
+path.append() {
    # For each passed dirname...
    local dirname
    for   dirname; do
        # Strip the trailing directory separator if any from this dirname,
        # reducing this dirname to the canonical form expected by the
        # test for uniqueness performed below.
        dirname="${dirname%/}"

        # If this dirname is either relative, duplicate, or nonextant, then
        # silently ignore this dirname and continue to the next. Note that the
        # extancy test is the least performant test and hence deferred.
        [[ "${dirname:0:1}" == '/' &&
           ":${PATH}:" != *":${dirname}:"* &&
           -d "${dirname}" ]] || continue

        # Else, this is an existing absolute unique dirname. In this case,
        # append this dirname to the current ${PATH}.
        PATH="${PATH}:${dirname}"
    done

    # Strip an erroneously leading delimiter from the current ${PATH} if any,
    # a common edge case when the initial ${PATH} is the empty string.
    PATH="${PATH#:}"

    # Export the current ${PATH} to subprocesses. Although system-wide scripts
    # already export the ${PATH} by default on most systems, "Bother free is
    # the way to be."
    export PATH
}

จงเตรียมใจให้พร้อมรับรัศมีภาพทันที จากนั้นแทนที่จะทำสิ่งนี้และหวังว่าจะได้สิ่งที่ดีที่สุด:

export PATH=$PATH:~/opt/bin:~/the/black/goat/of/the/woods/with/a/thousand/young

ทำสิ่งนี้แทนและรับรองว่าจะได้รับสิ่งที่ดีที่สุดไม่ว่าคุณจะต้องการมันหรือไม่ก็ตาม:

+path.append ~/opt/bin ~/the/black/goat/of/the/woods/with/a/thousand/young

ดีมากกำหนด "ดีที่สุด"

การต่อท้ายและต่อเติมอย่างปลอดภัยในปัจจุบัน${PATH}ไม่ใช่เรื่องเล็ก ๆ น้อย ๆ ที่เกิดขึ้นโดยทั่วไป ในขณะที่ความสะดวกสบายและสมเหตุสมผลดูเหมือนหนึ่งในรูปแบบตอร์ปิโดexport PATH=$PATH:~/opt/binเชิญภาวะแทรกซ้อนมารด้วย:

  • ชื่อเล่นที่สัมพันธ์กันโดยบังเอิญ (เช่นexport PATH=$PATH:opt/bin) ในขณะที่bashและzshเงียบยอมรับและส่วนใหญ่ไม่สนใจ dirnames ญาติในที่สุดกรณี dirnames ญาตินำหน้าโดยทั้งhหรือt(และอาจจะตัวละครที่ชั่วร้ายอื่น ๆ ) สาเหตุทั้งประเจิดประเจ้อทำลายตัวเอง Ala มาซากิโคบายาชิของน้ำเชื้อ 1962 ชิ้นเอกการฆ่าตัวตาย :

    # Don't try this at home. You will feel great pain.
    $ PATH='/usr/local/bin:/usr/bin:/bin' && export PATH=$PATH:harakiri && echo $PATH
    /usr/local/bin:/usr/bin:arakiri
    $ PATH='/usr/local/bin:/usr/bin:/bin' && export PATH=$PATH:tanuki/yokai && echo $PATH
    binanuki/yokai   # Congratulations. Your system is now face-up in the gutter.
  • ชื่อเล่นที่ซ้ำกันโดยบังเอิญ ในขณะที่ชื่อ${PATH}dirnames ซ้ำ ๆนั้นไม่มีอันตรายส่วนใหญ่พวกมันยังไม่พึงประสงค์ยุ่งยากไม่มีประสิทธิภาพเล็กน้อยขัดขวางการ debuggability และส่งเสริมการสึกหรอของไดรฟ์ - sorta เหมือนคำตอบนี้ ขณะ SSDs NAND สไตล์ ( แน่นอน ) ภูมิคุ้มกันเพื่ออ่านสวมใส่ฮาร์ดดิสก์ไดรฟ์ไม่ได้ การเข้าถึงระบบไฟล์ที่ไม่จำเป็นในทุกคำสั่งที่พยายามหมายถึงการสึกหรอของหัวอ่านที่ไม่จำเป็นในจังหวะเดียวกัน ซ้ำเป็นสอพลอเฉพาะอย่างยิ่งเมื่อการเรียกใช้เปลือกหอยที่ซ้อนกันในกระบวนการย่อยที่ซ้อนกันที่จุดที่ดูเหมือนไม่มีพิษมีภัยหนึ่งสมุทรเหมือนexport PATH=$PATH:~/watอย่างรวดเร็วระเบิดเข้าไปในเจ็ดวงกลมของนรกเช่น${PATH} PATH=/usr/local/bin:/usr/bin:/bin:/home/leycec/wat:/home/leycec/wat:/home/leycec/wat:/home/leycec/watมีเพียง Beelzebubba เท่านั้นที่สามารถช่วยคุณได้ถ้าคุณต่อท้ายชื่อเล่นเพิ่มเติมเข้าไป (อย่าปล่อยให้เรื่องนี้เกิดขึ้นกับลูกที่มีค่าของคุณ )

  • ชื่อผู้ใช้หายไปโดยไม่ได้ตั้งใจ อีกครั้งในขณะที่ชื่อ${PATH}dirnames ที่หายไปนั้นไม่มีอันตรายส่วนใหญ่ แต่โดยทั่วไปแล้วพวกเขายังไม่พึงประสงค์ยุ่งยากยุ่งยากไม่มีประสิทธิภาพเล็กน้อยขัดขวางการ debuggability และส่งเสริมการสึกหรอของไดรฟ์

ดังนั้นระบบอัตโนมัติที่เป็นมิตรเช่นฟังก์ชั่นของเชลล์ที่กำหนดไว้ด้านบน เราต้องช่วยตัวเองให้พ้นจากตัวเราเอง

แต่ ... ทำไม "+ path.append ()" ทำไมไม่เพียงแค่ append_path ()

สำหรับ disambiguity (เช่นมีคำสั่งภายนอกในปัจจุบัน${PATH}ฟังก์ชั่นเปลือกหรือทั้งระบบที่กำหนดไว้ที่อื่น ๆ ) ที่ผู้ใช้กำหนดฟังก์ชั่นเปลือกจะมีคำนำหน้านึกคิดหรือ suffixed กับสตริงที่ไม่ซ้ำกันโดยได้รับการสนับสนุนbashและzshแต่ห้ามมิฉะนั้นสำหรับ basenames คำสั่งมาตรฐานที่ชอบ - +พูด

เฮ้ มันได้ผล. อย่าตัดสินฉัน

แต่ ... ทำไม "+ path.append ()" ทำไมไม่ "+ path.prepend ()"

เนื่องจากการต่อท้ายกระแส${PATH}นั้นปลอดภัยกว่าการต่อท้ายกับ${PATH}ทุกสิ่งเท่ากันซึ่งไม่เคยเป็น การเอาชนะคำสั่งทั้งระบบด้วยคำสั่งเฉพาะของผู้ใช้นั้นอาจไม่ดีต่อสิ่งที่ดีที่สุดและทำสิ่งที่แย่ที่สุด ภายใต้ Linux ตัวอย่างเช่นแอพพลิเคชั่นแบบดาวน์สตรีมโดยทั่วไปคาดหวังว่าGNU coreutils จะแตกต่างจากคำสั่งแทนที่จะเป็นอนุพันธ์หรือทางเลือกที่ไม่ได้มาตรฐาน

ที่กล่าวว่ามีกรณีใช้อย่างถูกต้องสำหรับการทำเช่นนั้น การกำหนด+path.prepend()ฟังก์ชั่นที่เทียบเท่านั้นเป็นเรื่องเล็กน้อย Sans prolix nebulosity สำหรับเขาและเธอที่แบ่งปัน:

+path.prepend() {
    local dirname
    for dirname in "${@}"; do
        dirname="${dirname%/}"
        [[ "${dirname:0:1}" == '/' &&
           ":${PATH}:" != *":${dirname}:"* &&
           -d "${dirname}" ]] || continue
        PATH="${dirname}:${PATH}"
    done
    PATH="${PATH%:}"
    export PATH
}

แต่ ... ทำไมต้องไม่ใช่กิลส์?

กิลส์ ' คำตอบที่ได้รับการยอมรับที่อื่นจะดีที่สุดประทับใจในกรณีทั่วไปเป็น'เปลือกผนวก idempotent ไม่เชื่อเรื่องพระเจ้า' ในกรณีที่พบบ่อยของbashและzshมีไม่มี symlinks ที่ไม่พึงประสงค์ แต่ลงโทษประสิทธิภาพที่จำเป็นในการทำเช่นนั้นเศร้าricer Gentooในตัวผม แม้จะadd_to_PATH()มี symlink ที่ไม่พึงประสงค์ก็ยังเป็นที่ถกเถียงกันว่าการฟอร์กหนึ่ง subshell ต่อการโต้แย้งนั้นคุ้มค่ากับการแทรก symlink ที่มีศักยภาพ

สำหรับกรณีการใช้งานที่เข้มงวดซึ่งเรียกร้องให้กำจัด symlink ที่ซ้ำซ้อนzshตัวแปรเฉพาะนี้ทำได้โดยใช้ builtins ที่มีประสิทธิภาพแทนที่จะใช้ส้อมที่ไม่มีประสิทธิภาพ:

+path.append() {
    local dirname
    for   dirname in "${@}"; do
        dirname="${dirname%/}"
        [[ "${dirname:0:1}" == '/' &&
           ":${PATH}:" != *":${dirname:A}:"* &&
           -d "${dirname}" ]] || continue
        PATH="${PATH}:${dirname}"
    done
    PATH="${PATH#:}"
    export PATH
}

สังเกตการ*":${dirname:A}:"*แทนที่*":${dirname}:"*ของต้นฉบับ :Aเป็นมหัศจรรย์zsh-ism เศร้าขาดภายใต้เปลือกหอยอื่น ๆ ส่วนใหญ่ - bashรวมทั้ง อ้างถึงman zshexpn:

ตอบ : เปลี่ยนชื่อไฟล์ให้เป็นพา ธ สัมบูรณ์ตามที่aตัวดัดแปลงทำแล้วส่งผลลัพธ์ผ่านrealpath(3)ฟังก์ชั่นไลบรารีเพื่อแก้ไขลิงก์สัญลักษณ์ หมายเหตุ: สำหรับระบบที่ไม่มีrealpath(3)ฟังก์ชันไลบรารีลิงก์สัญลักษณ์จะไม่ได้รับการแก้ไขดังนั้นในระบบเหล่านั้นaและAเทียบเท่า

ไม่มีคำถามเพิ่มเติม

ไม่เป็นไร สนุกกับการปอกเปลือกอย่างปลอดภัย ตอนนี้คุณสมควรได้รับมัน


0

นี่คือรูปแบบการเขียนโปรแกรมฟังก์ชั่นของฉัน

  • การทำงานสำหรับลำไส้ใหญ่คั่นใดตัวแปรไม่เพียง แต่*PATHPATH
  • ไม่เข้าถึงสถานะโกลบอล
  • ใช้งานได้กับ / บนอินพุตที่เปลี่ยนรูปแบบไม่ได้เท่านั้น
  • สร้างเอาต์พุตเดี่ยว
  • ไม่มีผลข้างเคียง
  • จดจำได้ (ตามหลักการ)

นอกจากนี้ที่น่าสังเกต:

  • ผู้ไม่เชื่อเรื่องพระเจ้าเกี่ยวกับexportไอเอ็นจี; ที่เหลือให้ผู้โทร (ดูตัวอย่าง)
  • เพียวbash; ไม่มีฟอร์กกิ้ง
path_add () {
  # $ 1: องค์ประกอบเพื่อให้แน่ใจว่าอยู่ในสตริงเส้นทางที่กำหนดหนึ่งครั้ง
  # $ 2: ค่าสตริงเส้นทางที่มีอยู่ ("$ PATH" ไม่ใช่ "PATH")
  # $ 3 (เป็นทางเลือกสิ่งใดก็ได้): ถ้ามีให้เพิ่ม $ 1 ต่อท้าย มิฉะนั้นเสริม
  #
  # ตัวอย่าง:
  # $ export PATH = $ (path_add '/ opt / bin' "$ PATH")
  # $ CDPATH = $ (path_add '/ Music' "$ CDPATH" at_end)

  local -r now_present = "(^ |:) $ {1} ($ | :)"
  ถ้า [["$ 2" = ~ $ only_present]]; แล้วก็
    echo "$ 2"
  elif [[$ # == 3]]; แล้วก็
    echo "$ {2}: $ {1}"
  อื่น
    echo "$ {1}: $ {2}"
  Fi
}

0

สคริปต์นี้ให้คุณเพิ่มในตอนท้ายของ$PATH:

PATH=path2; add_to_PATH after path1 path2:path3
echo $PATH
path2:path1:path3

หรือเพิ่มที่จุดเริ่มต้นของ$PATH:

PATH=path2; add_to_PATH before path1 path2:path3
echo $PATH
path1:path3:path2

# Add directories to $PATH iff they're not already there
# Append directories to $PATH by default
# Based on https://unix.stackexchange.com/a/4973/143394
# and https://unix.stackexchange.com/a/217629/143394
add_to_PATH () {
  local prepend  # Prepend to path if set
  local prefix   # Temporary prepended path
  local IFS      # Avoid restoring for added laziness

  case $1 in
    after)  shift;; # Default is to append
    before) prepend=true; shift;;
  esac

  for arg; do
    IFS=: # Split argument by path separator
    for dir in $arg; do
      # Canonicalise symbolic links
      dir=$({ cd -- "$dir" && { pwd -P || pwd; } } 2>/dev/null)
      if [ -z "$dir" ]; then continue; fi  # Skip non-existent directory
      case ":$PATH:" in
        *":$dir:"*) :;; # skip - already present
        *) if [ "$prepend" ]; then
           # ${prefix:+$prefix:} will expand to "" if $prefix is empty to avoid
           # starting with a ":".  Expansion is "$prefix:" if non-empty.
            prefix=${prefix+$prefix:}$dir
          else
            PATH=$PATH:$dir  # Append by default
          fi;;
      esac
    done
  done
  [ "$prepend" ] && [ "$prefix" != "" ] && PATH=$prefix:$PATH
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.