Thêm thư mục vào $ PATH nếu nó chưa có


126

Có ai đã viết một hàm bash để thêm một thư mục vào $ PATH chỉ khi nó chưa có ở đó?

Tôi thường thêm vào PATH bằng cách sử dụng một cái gì đó như:

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

Nếu tôi xây dựng PATH của mình trong .bash_profile, thì nó sẽ không được đọc trừ khi phiên tôi tham gia là phiên đăng nhập - điều này không phải lúc nào cũng đúng. Nếu tôi xây dựng PATH của mình trong .bashrc, thì nó sẽ chạy với mỗi lớp con. Vì vậy, nếu tôi khởi chạy một cửa sổ Terminal và sau đó chạy màn hình và sau đó chạy một kịch bản shell, tôi nhận được:

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

Tôi sẽ thử xây dựng một hàm bash được gọi là add_to_path()chỉ thêm thư mục nếu nó không có ở đó. Nhưng, nếu bất cứ ai đã viết (hoặc tìm thấy) một điều như vậy, tôi sẽ không dành thời gian cho nó.


Xem stackoverflow.com/questions/273909/ trên để biết một số cơ sở hạ tầng có thể trợ giúp.
dmckee


Nếu bạn đặt vấn đề là "chỉ thêm nếu chưa có", bạn sẽ ngạc nhiên một cách thô lỗ khi ngày đến khi điều quan trọng là vật phẩm được chèn vào lúc ban đầu nhưng nó không xuất hiện ở đó. Một cách tiếp cận tốt hơn sẽ là chèn phần tử, và sau đó loại bỏ các bản sao, vì vậy nếu mục nhập mới đã có, nó sẽ được chuyển một cách hiệu quả ngay từ đầu.
Don nở

Câu trả lời:


125

Từ .bashrc của tôi:

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

Lưu ý rằng PATH phải được đánh dấu là đã xuất, do đó không cần nhập lại. Điều này kiểm tra xem thư mục có tồn tại & là một thư mục trước khi thêm nó, mà bạn có thể không quan tâm.

Ngoài ra, điều này thêm thư mục mới vào cuối đường dẫn; để đặt ở đầu, sử dụng PATH="$1${PATH:+":$PATH"}"thay vì PATH=dòng trên .


26
Tôi quan tâm.
Dennis Williamson

4
@Neil: Nó hoạt động được, bởi vì nó so sánh với ":$PATH:"thay vì chỉ"$PATH"
Gordon Davisson

3
@GordonDavisson: Tôi xin lỗi, bài kiểm tra của tôi đã sai và bạn đã đúng.
Neil

2
@GordonDavisson Điểm của các công cụ trong dấu ngoặc nhọn là gì. Tôi dường như không thể đánh đố nó ra " ${PATH:+"$PATH:"}$ 1"
thuyền viên

5
@ Mark0978: Đó là những gì tôi đã làm để khắc phục vấn đề bukzor đã chỉ ra. ${variable:+value}có nghĩa là kiểm tra xem có variableđược xác định và có giá trị không trống hay không và liệu nó có cho kết quả đánh giá hay không value. Về cơ bản, nếu PATH không trống để bắt đầu, nó đặt thành "$PATH:$1"; nếu nó trống, nó đặt thành chỉ "$1"(lưu ý thiếu dấu hai chấm).
Gordon Davisson

19

Mở rộng câu trả lời của Gordon Davisson, điều này hỗ trợ nhiều đối số

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

Vì vậy, bạn có thể thực hiện pathappend path1 path2 path3 ...

Để trả trước,

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

Tương tự như pathappend, bạn có thể làm

pathprepend path1 path2 path3 ...


3
Điều đó thật tuyệt! Tôi đã thực hiện một thay đổi nhỏ. Đối với chức năng 'pathprepend', thật thuận tiện để đọc các đối số ngược lại, ví dụ như bạn có thể nói pathprepend P1 P2 P3và kết thúc bằng PATH=P1:P2:P3. Để có được hành vi này, hãy đổi for ARG in "$@" dothànhfor ((i=$#; i>0; i--)); do ARG=${!i}
ishmael

Cảm ơn @ishmael, đề nghị tốt, tôi đã chỉnh sửa câu trả lời. Tôi nhận ra nhận xét của bạn đã hơn hai tuổi, nhưng tôi đã không trở lại kể từ đó. Tôi phải tìm ra cách để có được email trao đổi ngăn xếp để hạ cánh trong hộp thư đến của tôi!
Guillaume Perrault-Archambault

14

Đây là một cái gì đó từ câu trả lời của tôi cho câu hỏi này kết hợp với cấu trúc chức năng của Doug Harris. Nó sử dụng các biểu thức chính quy Bash:

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

Điều này chỉ hiệu quả với tôi khi sử dụng $1thay vì${1}
Andrei

@Andrei: Có, niềng răng là không cần thiết trong trường hợp này. Tôi không chắc tại sao tôi lại đưa chúng vào.
Dennis Williamson

10

Đặt ý kiến ​​này trong các bình luận cho câu trả lời đã chọn, nhưng các bình luận dường như không hỗ trợ định dạng PRE, vì vậy hãy thêm câu trả lời vào đây:

@ gordon-davisson Tôi không phải là một fan hâm mộ lớn của việc trích dẫn & ghép nối không cần thiết. Giả sử bạn đang sử dụng phiên bản bash> = 3, thay vào đó, bạn có thể sử dụng regexs tích hợp sẵn của bash và thực hiện:

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

Điều này không xử lý chính xác các trường hợp có không gian trong thư mục hoặc PATH. Có một số câu hỏi là liệu công cụ regex tích hợp của bash có đủ chậm để điều này có thể kém hiệu quả hơn so với phép nối và nội suy chuỗi mà phiên bản của bạn thực hiện hay không, nhưng bằng cách nào đó, nó chỉ mang lại cảm giác thẩm mỹ hơn cho tôi.


1
Nhận xét formatting using the backtickchỉ hỗ trợ nhưng bạn không nhận được bất kỳ kiểm soát đoạn nào.
thuyền viên

Điều này đặt bổ sung vào cuối. Nó thường được mong muốn để thêm vào đầu để ghi đè các vị trí hiện có.
Dennis Williamson

@DennisWilliamson Đó là một điểm công bằng, mặc dù tôi không khuyến nghị đó là hành vi mặc định. Không khó để tìm ra cách thay đổi cho một khoản trả trước.
Christopher Smith

@ChristopherSmith - re: unnecessary quotingngụ ý bạn biết trước thời hạn $PATHkhông phải là null. "$PATH"làm cho nó ổn dù PATH là null hay không. Tương tự nếu $1chứa các ký tự có thể gây nhầm lẫn cho trình phân tích cú pháp lệnh. Đặt regex trong dấu ngoặc kép "(^|:)$1(:|$)"ngăn chặn điều đó.
Jesse Chisholm

@JesseChisholm: Thật ra, tôi tin rằng quan điểm của Christopher là các quy tắc khác nhau giữa [[]]. Tôi thích trích dẫn mọi thứ có thể cần được trích dẫn, trừ khi điều đó khiến nó thất bại, nhưng tôi tin rằng anh ấy đúng, và những trích dẫn đó thực sự không cần thiết xung quanh $PATH. Mặt khác, dường như với tôi rằng bạn đúng $1.
Scott

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
}

Khi bạn cần $ HOME / thùng để xuất hiện chính xác một lần vào đầu $ PATH của bạn và không ở đâu khác, chấp nhận không có sản phẩm thay thế.


Cảm ơn, đó là một giải pháp thanh lịch tốt đẹp, nhưng tôi thấy rằng tôi phải làm PATH=${PATH/"... thay vì PATH=${PATH//"... để làm cho nó hoạt động.
Đánh dấu gian hàng

Hình thức gạch chéo kép phải phù hợp với bất kỳ số lượng trận đấu nào; dấu gạch chéo đơn chỉ khớp với dấu đầu tiên (tìm kiếm "Thay thế mẫu" trong trang bash man). Không chắc tại sao nó không hoạt động ...
andybuckley

Điều này thất bại trong trường hợp bất thường đó $1là mục duy nhất (không có dấu hai chấm). Các mục trở nên tăng gấp đôi.
Dennis Williamson

Nó cũng xóa quá mạnh mẽ như được chỉ ra bởi PeterS6g .
Dennis Williamson

6

Đây là một giải pháp thay thế có lợi thế bổ sung là loại bỏ các mục thừa:

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

Đối số duy nhất cho hàm này được thêm vào PATH và trường hợp đầu tiên của cùng một chuỗi được xóa khỏi đường dẫn hiện có. Nói cách khác, nếu thư mục đã tồn tại trong đường dẫn, nó được thăng cấp lên phía trước chứ không được thêm dưới dạng trùng lặp.

Hàm này hoạt động bằng cách thêm dấu hai chấm vào đường dẫn để đảm bảo rằng tất cả các mục có dấu hai chấm ở phía trước, và sau đó chuẩn bị mục mới cho đường dẫn hiện có với mục đó đã bị xóa. Phần cuối cùng được thực hiện bằng cách sử dụng ${var//pattern/sub}ký hiệu của bash ; xem hướng dẫn sử dụng bash để biết thêm chi tiết.


Tư tưởng tốt; thực hiện thiếu sót. Xem xét những gì xảy ra nếu bạn đã có /home/roberttrong bạn PATHvà bạn pathadd /home/rob.
Scott

5

Đây là của tôi (tôi tin rằng nó đã được viết bởi Oscar, sysadmin của phòng thí nghiệm cũ của tôi, tất cả đều là tín dụng của anh ấy), nó đã xuất hiện trong bashrc của tôi từ lâu. Nó có thêm lợi ích là cho phép bạn thêm trước hoặc nối thêm thư mục mới theo ý muốn:

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

Sử dụng:

$ 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

Để trả trước, tôi thích giải pháp của @ Russell, nhưng có một lỗi nhỏ: nếu bạn cố gắng thêm một cái gì đó như "/ bin" vào đường dẫn "/ sbin: / usr / bin: / var / usr / bin: / usr / local / bin: / usr / sbin "nó thay thế" / bin: "3 lần (khi nó không thực sự khớp với nhau). Kết hợp một bản sửa lỗi cho giải pháp bổ sung từ @ gordon-davisson, tôi nhận được điều này:

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

Một bí danh đơn giản như thế này dưới đây nên thực hiện mẹo:

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

Tất cả những gì nó làm là phân chia đường dẫn trên: ký tự và so sánh từng thành phần với đối số bạn truyền vào. Grep kiểm tra xem có khớp dòng hoàn chỉnh không và in ra số đếm.

Sử dụng mẫu:

$ 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

Thay thế lệnh echo bằng addToPath hoặc một số bí danh / hàm tương tự.


Sử dụng "grep -x" có thể nhanh hơn vòng lặp tôi đặt trong hàm bash.
Doug Harris


2

Đây là những gì tôi đã đánh lên:

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
}

Bây giờ trong .bashrc tôi có:

add_to_path /usr/local/mysql/bin

Phiên bản cập nhật sau bình luận về cách bản gốc của tôi sẽ không xử lý các thư mục có khoảng trắng (nhờ câu hỏi này để chỉ cho tôi sử dụng 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
Điều này có thể thất bại nếu bất kỳ tên thư mục đã có trong PATHchứa khoảng trắng, *, ?, hoặc [... ].
Scott

Điểm hay ... nhưng tôi là một anh chàng linux trường học cũ và sẽ không bao giờ sử dụng một con đường có khoảng trắng trong đó :-)
Doug Harris

Tốt cho bạn, vì không tạo ra những thứ có khoảng trắng trong tên của họ. Xấu hổ vì bạn đã viết mã không thể xử lý chúng khi chúng tồn tại. Và một người mà Linux, một người học Linux cũ phải làm gì với nó? Windoze có thể đã phổ biến ý tưởng này (cảm ơn bạn Documents and SettingsProgram Files), nhưng Unix đã hỗ trợ các tên đường dẫn chứa khoảng trắng từ trước khi Windoze tồn tại.
Scott

2

Tôi hơi ngạc nhiên khi chưa có ai đề cập đến vấn đề này, nhưng bạn có thể sử dụng readlink -fđể chuyển đổi các đường dẫn tương đối thành các đường dẫn tuyệt đối và thêm chúng vào PATH như vậy.

Ví dụ: để cải thiện câu trả lời của Guillaume Perrault-Archambault,

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

trở thành

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. Những điều cơ bản - Điều này có ích gì?

Các readlink -flệnh sẽ (trong số những thứ khác) chuyển đổi một đường dẫn tương đối đến một đường dẫn tuyệt đối. Điều này cho phép bạn làm một cái gì đó như

$ cd / đường dẫn / đến / của tôi / bin / dir
$ pathappend .
$ echo "$ PATH"
<your_old_path> : / path / to / my / bin / dir

2. Tại sao chúng ta thử nghiệm để được ở trong hai lần?

Vâng, hãy xem xét ví dụ trên. Nếu người dùng nói từ thư mục lần thứ hai, sẽ có . Tất nhiên, sẽ không có mặt trong . Nhưng sau đó sẽ được thiết lập để (đường dẫn tuyệt đối tương đương ), mà đã có trong . Vì vậy, chúng ta cần tránh thêm vào lần thứ hai.pathappend ./path/to/my/bin/dirARG..PATHARGA/path/to/my/bin/dir.PATH/path/to/my/bin/dirPATH

Có lẽ quan trọng hơn, mục đích chính của readlink, như tên của nó ngụ ý, để xem xét một liên kết tượng trưng và đọc tên đường dẫn mà nó chứa (nghĩa là trỏ đến). Ví dụ:

$ 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

Bây giờ, nếu bạn nói pathappend /usr/lib/perl/5.14, và bạn đã có /usr/lib/perl/5.14trong ĐƯỜNG của bạn, tốt, điều đó tốt; chúng ta có thể để nó như vậy Nhưng, nếu /usr/lib/perl/5.14chưa có trong PATH của bạn, chúng tôi gọi readlinkvà get ARGA= /usr/lib/perl/5.14.2, và sau đó chúng tôi thêm vào PATH. Nhưng hãy đợi một phút - nếu bạn đã nói pathappend /usr/lib/perl/5.14, thì bạn đã có /usr/lib/perl/5.14.2trong ĐƯỜNG của bạn, và, một lần nữa, chúng ta cần kiểm tra điều đó để tránh thêm nó vào PATHlần thứ hai.

3. Thỏa thuận với if ARGA=$(readlink -f "$ARG")cái gì?

Trong trường hợp không rõ ràng, dòng này kiểm tra xem liệu readlinkthành công. Đây chỉ là tốt, thực hành lập trình phòng thủ. Nếu chúng ta sẽ sử dụng đầu ra từ lệnh  m như một phần của lệnh  n (trong đó m  <  n ), thì nên kiểm tra xem lệnh  m có bị lỗi hay không và xử lý theo cách nào đó. Tôi không nghĩ rằng nó có khả năng readlinksẽ thất bại - nhưng, như đã thảo luận trong Cách lấy đường dẫn tuyệt đối của một tệp tùy ý từ OS X và các nơi khác, readlinklà một phát minh GNU. Nó không được chỉ định trong POSIX, vì vậy tính khả dụng của nó trong Mac OS, Solaris và các Unix không phải Linux khác là đáng nghi ngờ. (Trên thực tế, tôi vừa đọc một bình luận nói rằngreadlink -fdường như không hoạt động trên Mac OS X 10.11.6, nhưng realpathhoạt động vượt trội . Vì vậy, nếu bạn đang sử dụng hệ thống không có readlinkhoặc readlink -fkhông hoạt động, bạn có thể sửa đổi điều này script để sử dụng realpath.) Bằng cách cài đặt một mạng lưới an toàn, chúng tôi làm cho mã của chúng tôi dễ mang theo hơn.

Tất nhiên, nếu bạn trên một hệ thống không có readlink(hoặc  realpath), bạn sẽ không muốn làm .pathappend .

Bài -dkiểm tra thứ hai ( [ -d "$ARGA" ]) thực sự có lẽ không cần thiết. Tôi không thể nghĩ ra bất kỳ kịch bản nào $ARGlà thư mục và readlinkthành công, nhưng  $ARGAkhông phải là thư mục. Tôi chỉ sao chép và dán ifcâu lệnh đầu tiên để tạo câu lệnh thứ ba, và tôi rời khỏi  -dbài kiểm tra ở đó vì sự lười biếng.

4. Có ý kiến ​​nào khác không?

Vâng. Giống như nhiều câu trả lời khác ở đây, câu trả lời này kiểm tra xem mỗi đối số có phải là một thư mục hay không, xử lý nó nếu có và bỏ qua nó nếu không. Điều này có thể (hoặc có thể không) là đầy đủ nếu bạn pathappend chỉ sử dụng trong .các tập tin của EDR (như .bash_profile.bashrc) và các tập lệnh khác. Nhưng, như câu trả lời này cho thấy (ở trên), việc sử dụng nó một cách tương tác là hoàn toàn khả thi. Bạn sẽ rất bối rối nếu bạn làm

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

Bạn có nhận thấy rằng tôi đã nói nysql trong pathappendlệnh, hơn là mysql? Và điều đó pathappendkhông nói lên điều gì; Nó chỉ âm thầm bỏ qua lập luận không chính xác?

Như tôi đã nói ở trên, đó là cách thực hành tốt để xử lý lỗi. Đây là một ví dụ:

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) Bạn nên thêm dấu ngoặc kép : readlink -f "$ARG". (2) Tôi không biết tại sao điều đó sẽ xảy ra (đặc biệt là sau khi -d "$ARG"thử nghiệm thành công), nhưng bạn có thể muốn kiểm tra xem có readlinkthất bại hay không. (3) Dường như bạn readlinkđang xem chức năng chính của nó - để ánh xạ một tên liên kết tượng trưng đến một tên tệp thực sự của Cameron. Nếu (ví dụ) /binlà một liên kết tượng trưng đến /bin64, thì các cuộc gọi lặp đi lặp lại pathappend /bincó thể dẫn đến việc nói PATH …:/bin64:/bin64:/bin64:/bin64:…. Bạn có lẽ nên (cũng) kiểm tra xem giá trị mới $ARGđã có chưa PATH.
Scott

(1) Quan sát tốt, tôi đã sửa nó. (2) trong trường hợp nào thì readlink sẽ thất bại? Giả sử rằng một đường dẫn là chính xác và nó đề cập đến một vị trí hợp lệ. (3) Tôi không chắc điều gì quyết định chức năng chính của readlink, tôi tin rằng hầu hết (nếu không phải tất cả?) Đường dẫn trong một hệ thống tập tin unix có thể được chia thành 2 loại liên kết, liên kết tượng trưng và liên kết cứng. Đối với mục nhập đường dẫn trùng lặp, bạn đúng, nhưng mục đích của câu trả lời của tôi không phải là thêm điều đó (như tôi đã nhận thấy rằng các câu trả lời khác đã đề cập đến nó). Lý do duy nhất khiến tôi thêm một câu trả lời khác là đóng góp thứ mà tôi nghĩ chưa được đóng góp
qwertyzw

(2) Ý tôi là, nếu ít nhất tên của lệnh ngụ ý / ám chỉ mục đích của nó, thì 'link' trong readlink có thể đề cập đến các liên kết cứng hoặc mềm. Tuy nhiên, bạn đã đúng: man readlink nói 'readlink - in các liên kết tượng trưng được giải quyết hoặc tên tệp chính tắc', .trong ví dụ của tôi, tôi tin rằng có thể được coi là tên tệp chính tắc. Chính xác?
qwertyzw

(1) Đối với những người hiểu các liên kết tượng trưng, readlinktên của nó rõ ràng ngụ ý mục đích của nó - nó nhìn vào một liên kết tượng trưng và đọc tên đường dẫn mà nó chứa (nghĩa là trỏ đến). (2) Chà, tôi đã nói rằng tôi không biết tại sao readlinklại thất bại. Tôi đã đưa ra quan điểm chung rằng nếu một tập lệnh hoặc hàm chứa nhiều lệnh và lệnh  n được đảm bảo sẽ thất bại thảm hại (hoặc không có ý nghĩa gì cả) nếu lệnh  m thất bại (trong đó m  <  n ), thì đó là cách thực hành tốt kiểm tra xem lệnh m có thất bại hay không và xử lý theo cách nào đó - ít nhất là, ((tiếp theo)
Scott

(Tiếp), hủy bỏ tập lệnh / chức năng với chẩn đoán. Về mặt giả thuyết, readlinkcó thể thất bại nếu (a) thư mục bị đổi tên hoặc bị xóa (bởi một quá trình khác) giữa các cuộc gọi của bạn đến testreadlink, hoặc (b) nếu /usr/bin/readlinkbị xóa (hoặc bị hỏng). (3) Bạn dường như đang thiếu quan điểm của tôi. Tôi không khuyến khích bạn sao chép (các) câu trả lời khác; Tôi đang nói rằng, bằng cách kiểm tra xem bản gốc ARG(từ dòng lệnh) đã được nhập chưa PATH, nhưng không lặp lại kiểm tra cho cái mới ARG(đầu ra từ readlink), câu trả lời của bạn không đầy đủ và do đó không chính xác. Tiết (Cont'd)
Scott

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

1

Cách này hoạt động tốt:

if [[ ":$PATH:" != *":/new-directory:"* ]]; then PATH=${PATH}:/new-directory; fi

0

Các phiên bản của tôi ít cẩn thận hơn về các đường dẫn trống và khăng khăng các đường dẫn là hợp lệ và các thư mục so với một số được đăng ở đây, nhưng tôi tìm thấy một bộ sưu tập lớn về trả trước / chắp thêm / sạch / unique-ify / etc. các hàm shell sẽ hữu ích cho thao tác đường dẫn. Toàn bộ, ở trạng thái hiện tại của họ, có ở đây: http://pastebin.com/xS9sgQsX (phản hồi và cải tiến rất đáng hoan nghênh!)


0

Bạn có thể sử dụng một lớp lót 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;' "$@"
}

Đây là trong bash:

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

Tôi đã sửa đổi một chút câu trả lời của Gordon Davisson để sử dụng thư mục hiện tại nếu không được cung cấp. Vì vậy, bạn chỉ có thể làm paddtừ thư mục bạn muốn thêm vào PATH của bạn.

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

0

Bạn có thể kiểm tra xem một biến tùy chỉnh đã được đặt chưa, nếu không thì đặt nó và sau đó thêm các mục mới:

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

Tất nhiên, các mục này vẫn có thể được sao chép nếu được thêm bởi một tập lệnh khác, chẳng hạn như /etc/profile.


0

Kịch bản này cho phép bạn thêm vào cuối $PATH:

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

Hoặc thêm vào đầu $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

Đây là một cách tuân thủ 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"
        }
}

Nó được viết từ câu trả lời của Guillaume Perrault-Archambault cho câu hỏi này và câu trả lời của mike511 ở đây .

CẬP NHẬT 2017-11-23: Đã sửa lỗi mỗi @Scott


Tôi sẽ nâng cấp điều này để cung cấp tùy chọn dòng lệnh để lựa chọn giữa trả trước và trả sau (nối thêm), với một mặc định. Nhưng sau đó tôi nghĩ: đây là rất nhiều mã khó hiểu mà không có lời giải thích. (Và thực tế là bạn có hai chức năng, nơi người ta thay đổi PATH và lặp giá trị mới của nó, và người kia bắt đầu ragán nó vào PATH một lần nữa , chỉ là một sự phức tạp không cần thiết.) ... (Tiếp theo)
Scott

(Tiếp theo) Sau đó tôi nhận thấy rằng các liên kết đã sai. (Và tôi không chắc tại sao bạn lại đổ lỗi cho những kẻ đó; dường như bạn đã không sao chép nhiều từ câu trả lời của họ.) Và sau đó tôi nhận thấy rằng bị sai. Tôi đoán nó hoạt động tốt khi duy trì PATH được hình thành tốt, nhưng không có gì đảm bảo rằng nó đã được định hình tốt, đặc biệt là nếu bạn không được làm sáng tỏ /etc/profile. Thư mục mà bạn đang cố gắng thêm vào PATH có thể đã ở đó hơn một lần và mã của bạn bị bóp nghẹt. Tiết (Cont'd)
Scott

(Tiếp theo) ... Ví dụ, nếu bạn cố gắng thêm vào trước /a/ckđể /b:/a/ck:/tr:/a/ck, bạn nhận được /a/ck:/b:/a/ck:/tr:/tr:/a/ck.
Scott
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.