เพิ่มไดเรกทอรีไปยัง $ PATH หากยังไม่มี


126

มีใครเขียนฟังก์ชั่นทุบตีเพื่อเพิ่มไดเรกทอรีไปยัง $ PATH เท่านั้นหากยังไม่ได้มี?

ฉันมักจะเพิ่มเส้นทางโดยใช้สิ่งที่ชอบ:

export PATH=/usr/local/mysql/bin:$PATH

หากฉันสร้าง PATH ของฉันใน. bash_profile แสดงว่าไม่ได้อ่านยกเว้นว่าเซสชันที่ฉันใช้อยู่นั้นเป็นเซสชันการเข้าสู่ระบบซึ่งไม่ได้เป็นจริงเสมอไป ถ้าฉันสร้าง PATH ของฉันใน. bashrc มันจะทำงานกับแต่ละ subshell ดังนั้นถ้าฉันเปิดหน้าต่างเทอร์มินัลแล้วเรียกใช้หน้าจอแล้วเรียกใช้สคริปต์เชลล์ฉันจะได้รับ:

$ echo $PATH
/usr/local/mysql/bin:/usr/local/mysql/bin:/usr/local/mysql/bin:....

ฉันจะลองสร้างฟังก์ชั่นทุบตีที่เรียกว่าadd_to_path()เพิ่มเฉพาะไดเรกทอรีถ้ามันไม่ได้มี แต่ถ้าใครได้เขียน (หรือพบ) สิ่งนั้นฉันจะไม่ใช้เวลากับมัน


ดูstackoverflow.com/questions/273909/…สำหรับโครงสร้างพื้นฐานบางอย่างที่สามารถช่วยได้
dmckee


หากคุณระบุปัญหาว่า "เพิ่มเฉพาะหากยังไม่ได้อยู่ที่นั่น" คุณจะต้องประหลาดใจอย่างหยาบคายเมื่อวันนั้นมาถึงเมื่อสิ่งสำคัญสำหรับรายการที่แทรกอยู่ในตอนเริ่มต้น แต่มันไม่ได้จบอยู่ตรงนั้น วิธีที่ดีกว่าคือการแทรกองค์ประกอบและลบรายการที่ซ้ำกันดังนั้นหากรายการใหม่มีอยู่แล้วจะมีการย้ายไปที่จุดเริ่มต้นอย่างมีประสิทธิภาพ
Don Hatch

คำตอบ:


125

จาก. bashrc ของฉัน:

pathadd() {
    if [ -d "$1" ] && [[ ":$PATH:" != *":$1:"* ]]; then
        PATH="${PATH:+"$PATH:"}$1"
    fi
}

โปรดทราบว่า PATH ควรได้รับการทำเครื่องหมายว่าส่งออกแล้วดังนั้นจึงไม่จำเป็นต้องส่งออกอีกครั้ง การตรวจสอบนี้จะตรวจสอบว่าไดเรกทอรีนั้นมีอยู่ & เป็นไดเรกทอรีก่อนที่จะเพิ่มซึ่งคุณอาจไม่สนใจ

นอกจากนี้จะเพิ่มไดเรกทอรีใหม่ไปยังจุดสิ้นสุดของเส้นทาง เพื่อวางที่จุดเริ่มต้นใช้PATH="$1${PATH:+":$PATH"}"แทนPATH=บรรทัดข้างต้น


26
ฉันเป็นห่วง.
Dennis Williamson

4
@ Neil: มันใช้งานได้เพราะมันเปรียบเทียบกับ":$PATH:"แทนที่จะเป็นเพียงแค่"$PATH"
Gordon Davisson

3
@GordonDavisson: ฉันขอโทษการทดสอบของฉันผิดและคุณถูกต้อง
Neil

2
@GordonDavisson จุดของสิ่งของในวงเล็บปีกกาคืออะไร ฉันดูเหมือนจะไม่สามารถไขปริศนา " ${PATH:+"$PATH:"}$ 1"
boatcoder

5
@ Mark0978: นั่นคือสิ่งที่ฉันทำเพื่อแก้ไขปัญหา bukzor ชี้ให้เห็น ${variable:+value}หมายถึงการตรวจสอบว่าvariableมีการกำหนดและมีค่าที่ไม่ว่างเปล่าหรือไม่และจะให้ผลลัพธ์ของการประเมินvalueหรือไม่ โดยทั่วไปถ้า PATH นั้นไม่ว่างเปล่าเพื่อเริ่มต้นมันจะตั้งค่าเป็น"$PATH:$1"; ถ้ามันว่างเปล่ามันตั้งค่าเป็นเพียงแค่"$1"(หมายเหตุขาดลำไส้ใหญ่)
Gordon Davisson

19

การขยายตามคำตอบของ Gordon Davisson สิ่งนี้สนับสนุนอาร์กิวเมนต์หลายตัว

pathappend() {
  for ARG in "$@"
  do
    if [ -d "$ARG" ] && [[ ":$PATH:" != *":$ARG:"* ]]; then
        PATH="${PATH:+"$PATH:"}$ARG"
    fi
  done
}

ดังนั้นคุณสามารถทำ pathappend path1 path2 path3 ...

สำหรับการเตรียม

pathprepend() {
  for ((i=$#; i>0; i--)); 
  do
    ARG=${!i}
    if [ -d "$ARG" ] && [[ ":$PATH:" != *":$ARG:"* ]]; then
        PATH="$ARG${PATH:+":$PATH"}"
    fi
  done
}

คุณสามารถทำได้เช่นเดียวกับ pathappend

pathprepend path1 path2 path3 ...


3
มันเยี่ยมมาก! ฉันทำการเปลี่ยนแปลงเล็กน้อย สำหรับ 'pathprepend' ฟังก์ชั่นก็สะดวกในการอ่านข้อโต้แย้งในสิ่งที่ตรงกันข้ามเพื่อให้คุณสามารถพูดได้เช่นและจบลงด้วยpathprepend P1 P2 P3 PATH=P1:P2:P3หากต้องการรับพฤติกรรมนี้ให้เปลี่ยนfor ARG in "$@" doเป็นfor ((i=$#; i>0; i--)); do ARG=${!i}
ishmael

ขอบคุณ @ishmael คำแนะนำที่ดีฉันแก้ไขคำตอบ ฉันรู้ว่าความคิดเห็นของคุณมีอายุมากกว่าสองปี แต่ฉันไม่ได้กลับมาตั้งแต่ ฉันต้องหาวิธีรับอีเมลแลกเปลี่ยนแบบสแต็คเพื่อลงจอดในกล่องจดหมายของฉัน!
Guillaume Perrault-Archambault

14

นี่คือบางอย่างจากคำตอบของฉันไปที่คำถามนี้รวมกับโครงสร้างของฟังก์ชั่นดั๊กแฮร์ริส มันใช้นิพจน์ปกติของ Bash:

add_to_path ()
{
    if [[ "$PATH" =~ (^|:)"${1}"(:|$) ]]
    then
        return 0
    fi
    export PATH=${1}:$PATH
}

สิ่งนี้ใช้ได้กับฉันเท่านั้นใช้$1แทน${1}
อังเดร

@Andrei: ใช่การจัดฟันไม่จำเป็นในกรณีนี้ ฉันไม่แน่ใจว่าทำไมฉันรวมพวกเขา
Dennis Williamson

10

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

@ gordon-davisson ฉันไม่ได้เป็นแฟนตัวยงของการพูดไม่จำเป็น & การต่อกัน สมมติว่าคุณใช้ bash version> = 3 คุณสามารถใช้ regex ของ built-in และทำสิ่งต่อไปนี้

pathadd() {
    if [ -d "$1" ] && [[ ! $PATH =~ (^|:)$1(:|$) ]]; then
        PATH+=:$1
    fi
}

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


1
ความคิดเห็นสนับสนุนformatting using the backtickเพียง แต่คุณไม่ได้รับการควบคุมย่อหน้าที่เหมาะสม
boatcoder

สิ่งนี้ทำให้การเพิ่มในตอนท้าย บ่อยครั้งที่ต้องการเพิ่มไปยังจุดเริ่มต้นเพื่อแทนที่ตำแหน่งที่มีอยู่
Dennis Williamson

@DennisWilliamson นั่นเป็นประเด็นที่ดีแม้ว่าฉันจะไม่แนะนำให้ทำเช่นนี้ว่าเป็นพฤติกรรมเริ่มต้น ไม่ใช่เรื่องยากที่จะเข้าใจว่าจะเปลี่ยนแปลงอย่างไรสำหรับการต่อเติม
Christopher Smith

@ChristopherSmith - อีกครั้ง: unnecessary quotingบอกเป็นนัยว่าคุณรู้ล่วงหน้าว่า$PATHไม่เป็นโมฆะ "$PATH"ทำให้มันตกลงว่า PATH เป็นโมฆะหรือไม่ ในทำนองเดียวกันหาก$1มีอักขระที่อาจทำให้โปรแกรมแยกวิเคราะห์คำสั่งสับสน การใส่ regex ลงในเครื่องหมายคำพูด"(^|:)$1(:|$)"จะป้องกันไม่ให้
Jesse Chisholm

@JesseChisholm: จริงๆแล้วผมเชื่อว่าจุดคริสคือว่ากฎระเบียบที่มีความแตกต่างกันและ[[ ]]ฉันชอบที่จะพูดทุกอย่างที่อาจจะต้องมีการยกเว้นแต่ที่เป็นสาเหตุของการล้มเหลว แต่ผมเชื่อว่าเขามีสิทธิและที่พูดจริงๆไม่จำเป็นต้อง$PATHไปรอบ ๆ บนมืออื่น ๆ $1ก็ดูเหมือนว่าฉันว่าคุณมีสิทธิเกี่ยวกับ
สกอตต์

7
idempotent_path_prepend ()
{
    PATH=${PATH//":$1"/} #delete any instances in the middle or at the end
    PATH=${PATH//"$1:"/} #delete any instances at the beginning
    export PATH="$1:$PATH" #prepend to beginning
}

เมื่อคุณต้องการ $ HOME / bin ปรากฏอย่างแน่นอนหนึ่งครั้งที่จุดเริ่มต้นของ $ PATH และไม่มีที่อื่นให้ยอมรับไม่ทดแทน


ขอบคุณนั่นเป็นคำตอบที่สง่างาม แต่ฉันพบว่าฉันต้องทำPATH=${PATH/"... มากกว่าPATH=${PATH//"... เพื่อที่จะให้มันทำงาน
Mark Booth

รูปแบบเครื่องหมายทับสองชั้นควรจับคู่กับจำนวนของการแข่งขันใด ๆ เครื่องหมายทับเดียวตรงกับคำแรกเท่านั้น (ค้นหา "การแทนที่ลวดลาย" ในหน้า bash man) ไม่แน่ใจว่าทำไมมันไม่ทำงาน ...
andybuckley

สิ่งนี้ล้มเหลวในกรณีที่ผิดปกติซึ่ง$1เป็นเพียงรายการเดียว (ไม่มีเครื่องหมายโคลอน) รายการจะกลายเป็นสองเท่า
Dennis Williamson

นอกจากนี้ยังลบมากเกินไปอุกอาจเป็นแหลมออกโดยPeterS6g
Dennis Williamson

6

นี่คือโซลูชันทางเลือกที่มีข้อได้เปรียบเพิ่มเติมในการลบรายการที่ซ้ำซ้อน:

function pathadd {
    PATH=:$PATH
    PATH=$1${PATH//:$1/}
}

อาร์กิวเมนต์เดียวของฟังก์ชั่นนี้ได้รับการต่อท้ายไปที่ PATH และอินสแตนซ์แรกของสตริงเดียวกันจะถูกลบออกจากเส้นทางที่มีอยู่ กล่าวอีกนัยหนึ่งถ้าไดเรกทอรีมีอยู่แล้วในเส้นทางมันจะเลื่อนไปข้างหน้ามากกว่าที่จะเพิ่มเป็นที่ซ้ำกัน

ฟังก์ชั่นการทำงานโดยการเพิ่มลำไส้ใหญ่ไปยังเส้นทางเพื่อให้แน่ใจว่ารายการทั้งหมดมีลำไส้ใหญ่ที่ด้านหน้าและจากนั้นการเตรียมรายการใหม่ไปยังเส้นทางที่มีอยู่กับรายการที่ถูกลบออก ส่วนสุดท้ายจะดำเนินการโดยใช้${var//pattern/sub}สัญกรณ์ของทุบตี; ดูคู่มือทุบตีสำหรับรายละเอียดเพิ่มเติม


ความคิดที่ดี ข้อบกพร่องการใช้งาน พิจารณาสิ่งที่เกิดขึ้นถ้าคุณมีอยู่แล้ว/home/robertในของคุณให้คุณและPATH pathadd /home/rob
Scott

5

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

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

การใช้งาน:

$ echo $PATH
/bin/:/usr/local/bin/:/usr/bin
$ pathmunge /bin/
$ echo $PATH
/bin/:/usr/local/bin/:/usr/bin
$ pathmunge /sbin/ after
$ echo $PATH
/bin/:/usr/local/bin/:/usr/bin:/sbin/

5

สำหรับการต่อเติมฉันชอบวิธีแก้ปัญหาของ @ Russell แต่มีข้อผิดพลาดเล็กน้อย: หากคุณพยายามเติมบางสิ่งเช่น "/ bin" ไปยังเส้นทางของ "/ sbin: / usr / bin: / var / usr / bin: / usr / local / bin: / usr / sbin "มันจะแทนที่" / bin: "3 ครั้ง (เมื่อไม่ตรงกันเลย) เมื่อรวมการแก้ไขเข้ากับโซลูชันผนวกท้ายจาก @ gordon-davisson ฉันได้สิ่งนี้:

path_prepend() {
    if [ -d "$1" ]; then
        PATH=${PATH//":$1:"/:} #delete all instances in the middle
        PATH=${PATH/%":$1"/} #delete any instance at the end
        PATH=${PATH/#"$1:"/} #delete any instance at the beginning
        PATH="$1${PATH:+":$PATH"}" #prepend $1 or if $PATH is empty set to $1
    fi
}

4

นามแฝงง่ายๆแบบนี้ด้านล่างควรทำเคล็ดลับ:

alias checkInPath="echo $PATH | tr ':' '\n' | grep -x -c "

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

ตัวอย่างการใช้งาน:

$ checkInPath "/usr/local"
1
$ checkInPath "/usr/local/sbin"
1
$ checkInPath "/usr/local/sbin2"
0
$ checkInPath "/usr/local/" > /dev/null && echo "Yes" || echo "No"
No
$ checkInPath "/usr/local/bin" > /dev/null && echo "Yes" || echo "No"
Yes
$ checkInPath "/usr/local/sbin" > /dev/null && echo "Yes" || echo "No"
Yes
$ checkInPath "/usr/local/sbin2" > /dev/null && echo "Yes" || echo "No"
No

แทนที่คำสั่ง echo ด้วย addToPath หรือนามแฝง / ฟังก์ชันที่คล้ายกัน


การใช้ "grep -x" น่าจะเร็วกว่าลูปที่ฉันใส่ลงไปในฟังก์ชั่นทุบตี
Doug Harris


2

นี่คือสิ่งที่ฉันทำขึ้น:

add_to_path ()
{
    path_list=`echo $PATH | tr ':' ' '`
    new_dir=$1
    for d in $path_list
    do
        if [ $d == $new_dir ]
        then
            return 0
        fi
    done
    export PATH=$new_dir:$PATH
}

ตอนนี้ใน. bashrc ฉันมี:

add_to_path /usr/local/mysql/bin

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

add_to_path ()
{
    new_dir=$1
    local IFS=:
    for d in $PATH
    do
        if [[ "$d" == "$new_dir" ]]
        then
            return 0
        fi
    done
    export PATH=$new_dir:$PATH
}

1
นี้อาจล้มเหลวถ้าชื่อไดเรกทอรีใด ๆ แล้วในPATHมีช่องว่าง*, ?หรือ...[ ]
สกอตต์

จุดที่ดี ... แต่ฉันเป็นชายชราลินุกซ์และจะไม่ใช้เส้นทางที่มีช่องว่างอยู่ในนั้น :-)
Doug Harris

ดีสำหรับคุณที่จะไม่สร้างสิ่งที่มีช่องว่างในชื่อของพวกเขา ความอัปยศที่คุณเขียนโค้ดที่ไม่สามารถจัดการได้เมื่อมีอยู่ และการเป็น“ คนที่ใช้ลินุกซ์วัยเรียน” ต้องทำอะไรกับมัน? Windoze อาจทำให้แนวคิดนี้เป็นที่นิยม (ขอบคุณDocuments and SettingsและProgram Files) แต่ยูนิกซ์สนับสนุนชื่อพา ธ ที่มีช่องว่างตั้งแต่ก่อนที่ Windoze จะมีอยู่
Scott

2

ฉันรู้สึกประหลาดใจเล็กน้อยที่ไม่มีใครพูดถึงเรื่องนี้ แต่คุณสามารถใช้readlink -fในการแปลงเส้นทางสัมพัทธ์เป็นเส้นทางที่แน่นอนและเพิ่มลงในเส้นทางดังกล่าว

ตัวอย่างเช่นเพื่อปรับปรุงคำตอบของ Guillaume Perrault-Archambault

pathappend() {
  for ARG in "$@"
  do
    if [ -d "$ARG" ] && [[ ":$PATH:" != *":$ARG:"* ]]; then
        PATH="${PATH:+"$PATH:"}$ARG"
    fi
  done
}

กลายเป็น

pathappend() {
    for ARG in "$@"
    do
        if [ -d "$ARG" ] && [[ ":$PATH:" != *":$ARG:"* ]]
        then
            if ARGA=$(readlink -f "$ARG")               #notice me
            then
                if [ -d "$ARGA" ] && [[ ":$PATH:" != *":$ARGA:"* ]]
                then
                    PATH="${PATH:+"$PATH:"}$ARGA"
                fi
            else
                PATH="${PATH:+"$PATH:"}$ARG"
            fi
        fi
    done
}

1. พื้นฐาน - มันทำอะไรได้ดี?

readlink -fคำสั่งจะ (เหนือสิ่งอื่น) แปลงเส้นทางเทียบกับเส้นทางที่แน่นอน สิ่งนี้ช่วยให้คุณทำสิ่งที่ชอบ

$ cd / path / to / my / bin / dir
$ pathappend
$ echo "$ PATH"
<your_old_path> : / path / to / my / bin / dir

2. ทำไมเราทดสอบว่าอยู่ในเส้นทางที่สองหรือไม่

ลองพิจารณาตัวอย่างข้างต้น หากผู้ใช้กล่าวว่า จากไดเรกทอรีเป็นครั้งที่สอง จะเป็น แน่นอนว่าจะไม่อยู่ใน แต่แล้วจะถูกตั้งค่า (เทียบเท่าเส้นทางที่แน่นอนของ) ซึ่งเป็นแล้วใน ดังนั้นเราจึงจำเป็นที่จะต้องหลีกเลี่ยงการเพิ่มที่จะเป็นครั้งที่สองpathappend ./path/to/my/bin/dirARG..PATHARGA/path/to/my/bin/dir.PATH/path/to/my/bin/dirPATH

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

$ ls -ld /usr/lib/perl/5.14
-rwxrwxrwx  1   root   root    Sep  3  2015 /usr/lib/perl/5.14 -> 5.14.2
$ readlink /usr/lib/perl/5.14
5.14.2
$ readlink -f /usr/lib/perl/5.14
/usr/lib/perl/5.14.2

ตอนนี้ถ้าคุณพูดpathappend /usr/lib/perl/5.14และคุณมี/usr/lib/perl/5.14อยู่ในเส้นทางของคุณแล้วก็ไม่เป็นไร เราสามารถทิ้งมันไว้อย่างที่มันเป็น แต่ถ้า/usr/lib/perl/5.14ไม่ได้อยู่ในเส้นทางของคุณเราเรียกreadlinkและได้รับARGA= /usr/lib/perl/5.14.2แล้วเราเพิ่มที่PATHจะ แต่รอสักครู่ - ถ้าคุณอยู่แล้วกล่าวว่าpathappend /usr/lib/perl/5.14แล้วคุณมีอยู่แล้ว/usr/lib/perl/5.14.2ในเส้นทางของคุณและอีกครั้งเราจำเป็นต้องตรวจสอบเพื่อที่จะหลีกเลี่ยงการเพิ่มมันจะPATHเป็นครั้งที่สอง

3. อะไรคือข้อตกลงกับif ARGA=$(readlink -f "$ARG")?

ในกรณีที่ไม่ชัดเจนบรรทัดนี้จะทดสอบว่าreadlinkสำเร็จหรือไม่ นี่เป็นเพียงวิธีการเขียนโปรแกรมที่ดีและป้องกัน หากเรากำลังจะใช้เอาท์พุทจากคำสั่ง  m เป็นส่วนหนึ่งของคำสั่ง  n (โดยที่m  <  n ) ก็ควรระมัดระวังที่จะตรวจสอบว่าคำสั่ง  mล้มเหลวและจัดการมันในบางวิธี ฉันไม่คิดว่าเป็นไปได้ที่readlinkจะล้มเหลว - แต่ตามที่กล่าวไว้ในวิธีการเรียกเส้นทางสัมบูรณ์ของไฟล์โดยพลการจาก OS X และที่อื่น ๆreadlinkเป็นสิ่งประดิษฐ์ของ GNU มันไม่ได้ระบุใน POSIX ดังนั้นความพร้อมใช้งานใน Mac OS, Solaris และ Unixes อื่น ๆ ที่ไม่ใช่ Linux น่าสงสัย (อันที่จริงฉันเพิ่งอ่านความคิดเห็นที่ระบุว่า“readlink -fดูเหมือนจะไม่ทำงานบน Mac OS X 10.11.6 แต่realpathทำงานนอกกรอบได้” ดังนั้นหากคุณอยู่ในระบบที่ไม่มีreadlinkหรือที่readlink -fใช้งานไม่ได้คุณอาจแก้ไขได้ สคริปต์ที่จะใช้realpath) โดยการติดตั้งเครือข่ายความปลอดภัยเราทำให้โค้ดของเราค่อนข้างพกพามากขึ้น

แน่นอนถ้าคุณอยู่ในระบบที่ไม่ได้readlink(หรือ  realpath), คุณจะไม่ต้องการที่จะทำpathappend .

การ-dทดสอบครั้งที่สอง( [ -d "$ARGA" ]) อาจไม่จำเป็นจริงๆ ฉันไม่สามารถนึกถึงสถานการณ์ใด ๆ ที่$ARGเป็นไดเรกทอรีและreadlinkประสบความสำเร็จ แต่  $ARGAไม่ใช่ไดเรกทอรี ฉันเพิ่งคัดลอกและวางifคำสั่งแรกเพื่อสร้างคำสั่งที่สามและฉันออกจากการ  -dทดสอบที่นั่นด้วยความเกียจคร้าน

4. ความคิดเห็นอื่น ๆ ?

ใช่. เช่นเดียวกับคำตอบอื่น ๆ มากมายที่นี่อันนี้ทดสอบว่าแต่ละอาร์กิวเมนต์เป็นไดเรกทอรีประมวลผลถ้ามันเป็นและไม่สนใจถ้ามันไม่ได้ สิ่งนี้อาจ (หรืออาจไม่) เพียงพอหากคุณใช้pathappend เฉพาะใน.ไฟล์“” (เช่น.bash_profileและ.bashrc) และสคริปต์อื่น ๆ แต่ดังที่คำตอบนี้แสดงให้เห็น (ด้านบน) มันเป็นไปได้อย่างสมบูรณ์แบบที่จะใช้มันแบบโต้ตอบ คุณจะงงมากถ้าคุณทำ

$ pathappend /usr/local/nysql/bin
$ mysql
-bash: mysql: command not found

คุณสังเกตเห็นว่าฉันพูดnysql ในpathappendคำสั่งมากกว่าmysql? และนั่นpathappendไม่ได้พูดอะไรเลย เป็นเพียงการเพิกเฉยต่ออาร์กิวเมนต์ที่ไม่ถูกต้องหรือไม่

ดังที่ฉันได้กล่าวไว้ข้างต้นเป็นวิธีปฏิบัติที่ดีในการจัดการข้อผิดพลาด นี่คือตัวอย่าง:

pathappend() {
    for ARG in "$@"
    do
        if [ -d "$ARG" ]
        then
            if [[ ":$PATH:" != *":$ARG:"* ]]
            then
                if ARGA=$(readlink -f "$ARG")           #notice me
                then
                    if [[ ":$PATH:" != *":$ARGA:"* ]]
                    then
                        PATH="${PATH:+"$PATH:"}$ARGA"
                    fi
                else
                    PATH="${PATH:+"$PATH:"}$ARG"
                fi
            fi
        else
            printf "Error: %s is not a directory.\n" "$ARG" >&2
        fi
    done
}

(1) readlink -f "$ARG"คุณควรเพิ่มคำพูด: (2) ฉันไม่รู้ว่าทำไมมันจะเกิดขึ้น (โดยเฉพาะหลังจากการ-d "$ARG"ทดสอบสำเร็จ) แต่คุณอาจต้องการทดสอบว่าreadlinkล้มเหลวหรือไม่ (3) ดูเหมือนว่าคุณจะมองเห็นreadlinkฟังก์ชั่นหลักของ - เพื่อแมปชื่อลิงก์สัญลักษณ์กับ "ชื่อไฟล์จริง" ถ้า (ตัวอย่าง) /binการเชื่อมโยงสัญลักษณ์การ/bin64โทรแล้วซ้ำอาจส่งผลในเส้นทางบอกว่าpathappend /bin …:/bin64:/bin64:/bin64:/bin64:…คุณอาจจะ (ยัง) ตรวจสอบว่าค่าใหม่ของที่มีอยู่แล้วใน$ARG PATH
สกอตต์

(1) การสังเกตที่ดีฉันแก้ไขมัน (2) readlink จะล้มเหลวในกรณีใด? สมมติว่าเส้นทางนั้นถูกต้องและมันหมายถึงตำแหน่งที่ถูกต้อง (3) ฉันไม่แน่ใจว่าหน้าที่หลักของ readlink คืออะไรฉันเชื่อว่าเส้นทางส่วนใหญ่ (ถ้าไม่ใช่ทั้งหมด) ในระบบไฟล์ยูนิกซ์สามารถแบ่งออกเป็น 2 ประเภทคือลิงก์ลิงก์สัญลักษณ์และฮาร์ดลิงก์ สำหรับรายการเส้นทางที่ซ้ำกันคุณถูกต้อง แต่จุดประสงค์ของคำตอบของฉันคือไม่เพิ่ม (ดังที่ฉันสังเกตเห็นว่าคำตอบอื่น ๆ ได้กล่าวถึงแล้ว) เหตุผลเดียวที่ฉันเพิ่มอีกคำตอบก็คือมีส่วนร่วมในสิ่งที่ฉันคิดว่ายังไม่ได้มีส่วนร่วม
qwertyzw

(2) ฉันหมายถึงถ้าอย่างน้อยที่สุดชื่อของคำสั่งโดยนัย / นัยตามวัตถุประสงค์แล้ว 'ลิงก์' ใน readlink สามารถอ้างถึงลิงก์ที่แข็งหรืออ่อนก็ได้ คุณถูกต้อง แต่: man readlink บอกว่า 'readlink - พิมพ์ลิงก์สัญลักษณ์ที่ได้รับการแก้ไขหรือชื่อไฟล์บัญญัติ' .ในตัวอย่างของฉันฉันเชื่อว่าถือได้ว่าเป็นชื่อไฟล์บัญญัติ แก้ไข?
qwertyzw

(1) สำหรับผู้ที่เข้าใจลิงก์สัญลักษณ์readlinkชื่อของมันบ่งบอกถึงวัตถุประสงค์อย่างชัดเจน - มันจะดูที่ลิงก์สัญลักษณ์และอ่านชื่อพา ธ ที่มี (เช่นชี้ไปที่) (2) ฉันพูดว่าไม่รู้ว่าทำไมreadlinkจะล้มเหลว ฉันสร้างประเด็นทั่วไปว่าหากสคริปต์หรือฟังก์ชั่นมีหลายคำสั่งและคำสั่ง  nรับประกันได้ว่าล้มเหลวอย่างหายนะ (หรือไม่สมเหตุสมผลเลย) หากคำสั่ง  mล้มเหลว (โดยที่m  <  n ) มันเป็นวิธีปฏิบัติที่ดีอย่างรอบคอบตรวจสอบว่าคำสั่งmล้มเหลวและจัดการในบางวิธี - อย่างน้อยที่สุด ... (ต่อ)
สกอตต์

(ต่อ) ... ยกเลิกสคริปต์ / ฟังก์ชันด้วยการวินิจฉัย สมมุติฐาน, readlinkอาจล้มเหลวหาก (a) ไดเรกทอรีถูกเปลี่ยนชื่อหรือลบ (โดยกระบวนการอื่น) ระหว่างการโทรไปยังtestและreadlinkหรือ (b) หาก/usr/bin/readlinkถูกลบ (หรือเสียหาย) (3) คุณดูเหมือนจะพลาดจุดของฉัน ฉันไม่สนับสนุนให้คุณทำซ้ำคำตอบอื่น ๆ ; ฉันกำลังบอกว่าโดยการตรวจสอบว่าต้นฉบับARG(จากบรรทัดคำสั่ง) มีอยู่แล้วPATHแต่ไม่ทำซ้ำการตรวจสอบใหม่ARG(ผลลัพธ์จากreadlink) คำตอบของคุณไม่สมบูรณ์และดังนั้นจึงไม่ถูกต้อง … (ต่อ)
สกอตต์

1
function __path_add(){  
if [ -d "$1" ] ; then  
    local D=":${PATH}:";   
    [ "${D/:$1:/:}" == "$D" ] && PATH="$PATH:$1";  
    PATH="${PATH/#:/}";  
    export PATH="${PATH/%:/}";  
fi  
}  


0

เวอร์ชันของฉันไม่ค่อยระมัดระวังเกี่ยวกับพา ธ ที่ว่างเปล่าและยืนยันในพา ธ ที่ถูกต้องและไดเรกทอรีกว่าบางโพสต์ที่นี่ แต่ฉันพบคอลเลกชันที่มีขนาดใหญ่ของการผนวก / ผนวก / clean / unique-ify / etc ฟังก์ชั่นเปลือกเพื่อเป็นประโยชน์สำหรับการจัดการเส้นทาง ล็อตทั้งหมดในสถานะปัจจุบันของพวกเขาอยู่ที่นี่: http://pastebin.com/xS9sgQsX (ข้อเสนอแนะและการปรับปรุงยินดีอย่างมาก!)


0

คุณสามารถใช้ Perl หนึ่งซับ:

appendPaths() { # append a group of paths together, leaving out redundancies
    # use as: export PATH="$(appendPaths "$PATH" "dir1" "dir2")
    # start at the end:
    #  - join all arguments with :,
    #  - split the result on :,
    #  - pick out non-empty elements which haven't been seen and which are directories,
    #  - join with :,
    #  - print
    perl -le 'print join ":", grep /\w/ && !$seen{$_}++ && -d $_, split ":", join ":", @ARGV;' "$@"
}

ที่นี่มันอยู่ในทุบตี:

addToPath() { 
    # inspired by Gordon Davisson, http://superuser.com/a/39995/208059
    # call as: addToPath dir1 dir2
    while (( "$#" > 0 )); do
    echo "Adding $1 to PATH."
    if [[ ! -d "$1" ]]; then
        echo "$1 is not a directory.";
    elif [[ ":$PATH:" == *":$1:"* ]]; then
        echo "$1 is already in the path."
    else
            export PATH="${PATH:+"$PATH:"}$1" # ${x:-defaultIfEmpty} ${x:+valueIfNotEmpty}
    fi
    shift
    done
}

0

ฉันแก้ไขคำตอบของ Gordon Davissonเล็กน้อยเพื่อใช้ dir ปัจจุบันหากไม่มีให้ ดังนั้นคุณสามารถทำได้paddจากไดเรกทอรีที่คุณต้องการจะเพิ่มใน PATH ของคุณ

padd() {
  current=`pwd`
  p=${1:-$current}
  if [ -d "$p" ] && [[ ":$PATH:" != *":$p:"* ]]; then
      PATH="$p:$PATH"
  fi
}

0

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

if [ "$MYPATHS" != "true" ]; then
    export MYPATHS="true"
    export PATH="$PATH:$HOME/bin:"

    # java stuff
    export JAVA_HOME="$(/usr/libexec/java_home)"
    export M2_HOME="$HOME/Applications/apache-maven-3.3.9"
    export PATH="$JAVA_HOME/bin:$M2_HOME/bin:$PATH"

    # etc...
fi

/etc/profileแน่นอนว่ารายการเหล่านี้จะยังคงได้ซ้ำหากมีการเพิ่มโดยสคริปต์อื่นเช่น


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" ] && PATH=$prefix:$PATH
}

0

นี่คือวิธีที่เป็นไปตาม POSIX

# USAGE: path_add [include|prepend|append] "dir1" "dir2" ...
#   prepend: add/move to beginning
#   append:  add/move to end
#   include: add to end of PATH if not already included [default]
#          that is, don't change position if already in PATH
# RETURNS:
# prepend:  dir2:dir1:OLD_PATH
# append:   OLD_PATH:dir1:dir2
# If called with no paramters, returns PATH with duplicate directories removed
path_add() {
    # use subshell to create "local" variables
    PATH="$(path_unique)"
    export PATH="$(path_add_do "$@")"
}

path_add_do() {
    case "$1" in
    'include'|'prepend'|'append') action="$1"; shift ;;
    *)                            action='include'   ;;
    esac

    path=":$PATH:" # pad to ensure full path is matched later

    for dir in "$@"; do
        #       [ -d "$dir" ] || continue # skip non-directory params

        left="${path%:$dir:*}" # remove last occurrence to end

        if [ "$path" = "$left" ]; then
            # PATH doesn't contain $dir
            [ "$action" = 'include' ] && action='append'
            right=''
        else
            right=":${path#$left:$dir:}" # remove start to last occurrence
        fi

        # construct path with $dir added
        case "$action" in
            'prepend') path=":$dir$left$right" ;;
            'append')  path="$left$right$dir:" ;;
        esac
    done

    # strip ':' pads
    path="${path#:}"
    path="${path%:}"

    # return
    printf '%s' "$path"
}

# USAGE: path_unique [path]
# path - a colon delimited list. Defaults to $PATH is not specified.
# RETURNS: `path` with duplicated directories removed
path_unique() {
    in_path=${1:-$PATH}
    path=':'

    # Wrap the while loop in '{}' to be able to access the updated `path variable
    # as the `while` loop is run in a subshell due to the piping to it.
    # https://stackoverflow.com/questions/4667509/shell-variables-set-inside-while-loop-not-visible-outside-of-it
    printf '%s\n' "$in_path" \
    | /bin/tr -s ':' '\n'    \
    | {
            while read -r dir; do
                left="${path%:$dir:*}" # remove last occurrence to end
                if [ "$path" = "$left" ]; then
                    # PATH doesn't contain $dir
                    path="$path$dir:"
                fi
            done
            # strip ':' pads
            path="${path#:}"
            path="${path%:}"
            # return
            printf '%s\n' "$path"
        }
}

มันเป็น cribbed จากกีโยมแปร์โรลท์-Archambault 'คำตอบของคำถามนี้และmike511 ' s คำตอบที่นี่

UPDATE 2017-11-23: แก้ไขข้อผิดพลาดต่อ @Scott


ฉันจะ upvote นี้เพื่อให้ตัวเลือกบรรทัดคำสั่งเพื่อเลือกระหว่างการเตรียมการและการเลื่อน (ต่อท้าย) โดยค่าเริ่มต้น แต่ฉันก็คิดว่า: นี่เป็นโค้ดที่ค่อนข้างลึกลับที่ไม่มีคำอธิบาย (และความจริงที่ว่าคุณมีสองฟังก์ชั่นที่หนึ่งการเปลี่ยนแปลงเส้นทางและสะท้อนมูลค่าใหม่และอื่น ๆ ที่จับว่าการส่งออกและกำหนดไปยังเส้นทางอีกครั้ง ,เป็นเพียงซับซ้อนที่ไม่จำเป็น.) ... (ต่อ)
สกอตต์

(ต่อ) …และจากนั้นฉันสังเกตเห็นว่าลิงก์ผิด (และฉันไม่แน่ใจว่าทำไมคุณถึงโทษคนพวกนี้คุณดูเหมือนจะไม่ได้คัดลอกอะไรมากมายจากคำตอบของพวกเขา) จากนั้นฉันสังเกตเห็นว่ารหัสผิด ผมคิดว่ามันไม่งานที่ตกลงของการรักษาที่ดีรูปแบบเส้นทาง แต่ไม่มีการรับประกันว่ามันไม่มีอยู่แล้ว/etc/profileดีขึ้นโดยเฉพาะอย่างยิ่งถ้าคุณมีทึบ ไดเรกทอรีที่คุณพยายามเพิ่มใน PATH อาจมีมากกว่าหนึ่งครั้งและรหัสของคุณฉายาไว้ … (ต่อ)
สกอตต์

(ต่อ) ... ตัวอย่างเช่นถ้าคุณพยายามที่จะย่อหน้า/a/ckเพื่อคุณจะได้รับ/b:/a/ck:/tr:/a/ck /a/ck:/b:/a/ck:/tr:/tr:/a/ck
สกอตต์
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.