Tambahkan direktori ke $ PATH jika belum ada di sana

126

Adakah yang menulis fungsi bash untuk menambahkan direktori ke $ PATH hanya jika belum ada di sana?

Saya biasanya menambahkan ke PATH menggunakan sesuatu seperti:

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

Jika saya membuat PATH saya di .bash_profile, maka itu tidak dibaca kecuali sesi yang saya masuki adalah sesi login - yang tidak selalu benar. Jika saya membangun PATH saya di .bashrc, maka itu berjalan dengan setiap subkulit. Jadi jika saya meluncurkan jendela Terminal dan kemudian menjalankan layar dan kemudian menjalankan skrip shell, saya mendapatkan:

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

Saya akan mencoba membangun fungsi bash yang disebut add_to_path()yang hanya menambahkan direktori jika tidak ada. Tetapi, jika ada yang sudah menulis (atau menemukan) hal seperti itu, saya tidak akan menghabiskan waktu untuk itu.

Doug Harris
sumber
Lihat stackoverflow.com/questions/273909/… untuk beberapa infrastruktur yang dapat membantu.
dmckee
unix.stackexchange.com/questions/4965/…
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件
Jika Anda membingkai masalah sebagai "hanya menambahkan jika belum ada di sana", Anda akan terkejut ketika hari itu tiba ketika penting untuk item yang dimasukkan berada di awal tetapi tidak berakhir di sana. Pendekatan yang lebih baik adalah menyisipkan elemen, dan kemudian menghapus duplikat, jadi jika entri baru sudah ada di sana akan secara efektif dipindahkan ke awal.
Don Hatch

Jawaban:

125

Dari .bashrc saya:

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

Perhatikan bahwa PATH harus sudah ditandai sebagai diekspor, jadi tidak perlu mengekspor ulang. Ini memeriksa apakah direktori tersebut ada & adalah direktori sebelum menambahkannya, yang mungkin tidak Anda pedulikan.

Juga, ini menambahkan direktori baru ke ujung jalan; untuk meletakkannya di awal, gunakan PATH="$1${PATH:+":$PATH"}"alih-alih PATH=baris di atas .

Gordon Davisson
sumber
26
Aku peduli.
Dennis Williamson
4
@ Neil: Itu berhasil, karena dibandingkan dengan ":$PATH:"bukan hanya"$PATH"
Gordon Davisson
3
@GordonDavisson: Saya minta maaf, pengujian saya salah dan Anda benar.
Neil
2
@GordonDavisson Apa gunanya barang-barang di kurung keriting. Sepertinya saya tidak bisa ${PATH:+"$PATH:"}
menebaknya
5
@ Mark0978: Itulah yang saya lakukan untuk memperbaiki masalah yang ditunjukkan oleh Bukzor. ${variable:+value}berarti memeriksa apakah variabledidefinisikan dan memiliki nilai yang tidak kosong, dan apakah itu memberikan hasil evaluasi value. Pada dasarnya, jika PATH bukan kosong untuk memulai dengan itu set ke "$PATH:$1"; jika itu kosong itu hanya menetapkan "$1"(perhatikan kurangnya titik dua).
Gordon Davisson
19

Memperluas jawaban Gordon Davisson, ini mendukung banyak argumen

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

Jadi Anda bisa melakukan pathappend path1 path2 path3 ...

Untuk mengawali,

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

Mirip dengan pathappend, Anda bisa melakukannya

pathprepend path1 path2 path3 ...

Guillaume Perrault-Archambault
sumber
3
Ini bagus! Saya membuat satu perubahan kecil. Untuk fungsi 'pathprepend', lebih mudah untuk membaca argumen secara terbalik, sehingga Anda dapat mengatakan, misalnya, pathprepend P1 P2 P3dan berakhir dengan PATH=P1:P2:P3. Untuk mendapatkan perilaku ini, ubah for ARG in "$@" dokefor ((i=$#; i>0; i--)); do ARG=${!i}
ishmael
Terima kasih @ ismail, saran yang bagus, saya mengedit jawabannya. Saya menyadari komentar Anda sudah lebih dari dua tahun, tetapi saya belum kembali lagi. Saya harus mencari cara untuk mendapatkan pertukaran pertukaran email agar dapat masuk ke kotak masuk saya!
Guillaume Perrault-Archambault
14

Inilah sesuatu dari jawaban saya untuk pertanyaan ini dikombinasikan dengan struktur fungsi Doug Harris. Ini menggunakan ekspresi reguler Bash:

add_to_path ()
{
    if [[ "$PATH" =~ (^|:)"${1}"(:|$) ]]
    then
        return 0
    fi
    export PATH=${1}:$PATH
}
Dennis Williamson
sumber
Ini bekerja untuk saya hanya menggunakan $1bukan${1}
Andrei
@Andrei: Ya, kawat gigi tidak perlu dalam hal ini. Saya tidak yakin mengapa saya memasukkannya.
Dennis Williamson
10

Letakkan ini di komentar untuk jawaban yang dipilih, tetapi komentar tampaknya tidak mendukung pemformatan PRE, jadi tambahkan jawabannya di sini:

@ gordon-davisson Saya bukan penggemar besar kutipan & penggabungan yang tidak perlu. Dengan asumsi Anda menggunakan versi bash> = 3, Anda bisa menggunakan bash's built in regexs dan lakukan:

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

Ini tidak benar menangani kasus-kasus di mana ada ruang dalam direktori atau PATH. Ada beberapa pertanyaan apakah bash's yang dibangun di mesin regex cukup lambat sehingga net ini mungkin kurang efisien daripada penggabungan string dan interpolasi yang versi Anda lakukan, tapi entah bagaimana itu terasa lebih estetis bersih untuk saya.

Christopher Smith
sumber
1
Komentar formatting using the backtickhanya mendukung tetapi Anda tidak mendapatkan kontrol paragraf yang layak.
boatcoder
Ini menempatkan penambahan di akhir. Sering diinginkan untuk menambahkan ke awal untuk menimpa lokasi yang ada.
Dennis Williamson
@ DennisWilliamson Itu poin yang adil, meskipun saya tidak akan merekomendasikan itu sebagai perilaku default. Tidak sulit untuk mengetahui cara mengubah untuk prepend.
Christopher Smith
@ChristopherSmith - re: unnecessary quotingmenyiratkan Anda tahu sebelumnya bahwa $PATHitu bukan nol. "$PATH"membuatnya OK apakah PATH adalah nol atau tidak. Demikian pula jika $1berisi karakter yang mungkin membingungkan parser perintah. Menempatkan regex dalam tanda kutip "(^|:)$1(:|$)"mencegah hal itu.
Jesse Chisholm
@JesseChisholm: Sebenarnya, saya percaya poin Christopher adalah bahwa aturannya berbeda antara [[dan ]]. Saya lebih memilih untuk mengutip segala sesuatu yang mungkin dapat perlu dikutip, kecuali yang menyebabkannya gagal, tapi saya yakin dia benar, dan bahwa mengutip benar-benar tidak diperlukan sekitar $PATH. Di sisi lain, menurut saya Anda benar tentang itu $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
}

Ketika Anda membutuhkan $ HOME / nampan untuk muncul tepat sekali di awal $ PATH Anda dan tempat lain, tidak menerima pengganti.

Russell
sumber
Terima kasih, itu solusi elegan yang bagus, tetapi saya menemukan bahwa saya harus melakukan PATH=${PATH/"... daripada PATH=${PATH//"... untuk membuatnya bekerja.
Mark Booth
Formulir double-slash harus cocok dengan jumlah pertandingan apa pun; slash tunggal hanya cocok dengan yang pertama (cari "substitusi pola" di halaman bash man). Tidak yakin mengapa itu tidak berhasil ...
andybuckley
Ini gagal dalam kasus yang tidak biasa yaitu $1satu-satunya entri (tanpa titik dua). Entri menjadi dua kali lipat.
Dennis Williamson
Itu juga menghapus terlalu agresif seperti yang ditunjukkan oleh PeterS6g .
Dennis Williamson
6

Berikut adalah solusi alternatif yang memiliki keuntungan tambahan untuk menghapus entru yang berlebihan:

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

Argumen tunggal untuk fungsi ini didahului dengan PATH, dan instance pertama dari string yang sama dihapus dari jalur yang ada. Dengan kata lain, jika direktori sudah ada di jalur, itu dipromosikan ke depan daripada ditambahkan sebagai duplikat.

Fungsi ini bekerja dengan menambahkan sebuah titik dua ke jalur untuk memastikan bahwa semua entri memiliki titik dua di bagian depan, dan kemudian menambahkan bagian baru ke jalur yang ada dengan entri yang dihapus. Bagian terakhir dilakukan menggunakan ${var//pattern/sub}notasi bash ; lihat manual bash untuk lebih jelasnya.

Rob Hague
sumber
Pemikiran yang bagus; implementasi cacat. Pertimbangkan apa yang terjadi jika Anda sudah memiliki /home/robertAnda PATHdan Anda pathadd /home/rob.
Scott
5

Ini milik saya (saya percaya itu ditulis bertahun-tahun yang lalu oleh Oscar, sysadmin dari lab lama saya, semua penghargaan kepadanya), sudah ada di bashrc saya selama berabad-abad. Ini memiliki manfaat tambahan yang memungkinkan Anda untuk menambahkan atau menambahkan direktori baru seperti yang diinginkan:

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

Pemakaian:

$ 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/
terdon
sumber
5

Untuk prepending, saya suka solusi @ Russell, tetapi ada bug kecil: jika Anda mencoba untuk menambahkan sesuatu seperti "/ bin" ke path "/ sbin: / usr / bin: / var / usr / bin: / usr / local / bin: / usr / sbin "itu menggantikan" / bin: "3 kali (ketika itu tidak benar-benar cocok sama sekali). Menggabungkan perbaikan untuk itu dengan solusi tambahan dari @ gordon-davisson, saya mendapatkan ini:

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
}
PeterS6g
sumber
4

Alias ​​sederhana seperti ini di bawah ini harus melakukan trik:

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

Yang dilakukannya hanyalah membagi jalur pada karakter: dan membandingkan setiap komponen dengan argumen yang Anda berikan. Grep memeriksa kecocokan garis lengkap, dan mencetak hitungan.

Penggunaan sampel:

$ 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

Ganti perintah echo dengan addToPath atau alias / fungsi serupa.

Nagul
sumber
Menggunakan "grep -x" mungkin lebih cepat daripada loop yang saya masukkan ke fungsi bash saya.
Doug Harris
2

Inilah yang saya siapkan:

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
}

Sekarang di .bashrc saya punya:

add_to_path /usr/local/mysql/bin

Versi terbaru mengikuti komentar tentang bagaimana dokumen asli saya tidak akan menangani direktori dengan spasi (berkat pertanyaan ini karena telah menunjukkan kepada saya untuk menggunakan 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
}
Doug Harris
sumber
1
Hal ini mungkin gagal jika ada nama direktori sudah PATHmengandung spasi, *, ?, atau [... ].
Scott
Poin bagus ... tapi saya seorang pria Linux jadul dan tidak akan pernah menggunakan jalur dengan spasi putih di dalamnya :-)
Doug Harris
Bagus untuk Anda, karena tidak menciptakan sesuatu dengan spasi putih dalam nama mereka. Malu pada Anda untuk menulis kode yang tidak dapat menangani mereka ketika mereka ada. Dan apa hubungannya dengan “seorang lelaki tua Linux” yang ada hubungannya dengan itu? Windoze mungkin telah mempopulerkan ide tersebut (terima kasih, Documents and Settingsdan Program Files), tetapi Unix telah mendukung nama path yang mengandung spasi sejak sebelum Windoze ada.
Scott
2

Saya agak terkejut bahwa belum ada yang menyebutkan ini, tetapi Anda dapat menggunakan readlink -funtuk mengubah jalur relatif ke jalur absolut, dan menambahkannya ke PATH.

Misalnya, untuk meningkatkan jawaban Guillaume Perrault-Archambault,

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

menjadi

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. Dasar-Dasar - Apa Manfaat Hal Ini?

The readlink -fperintah akan (antara lain) mengkonversi path relatif ke path absolut. Ini memungkinkan Anda melakukan sesuatu seperti

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

2. Mengapa Kami Menguji Berada di PATH Dua Kali?

Nah, perhatikan contoh di atas. Jika pengguna mengatakan dari direktori untuk kedua kalinya, akan . Tentu saja, tidak akan hadir di . Tapi kemudian akan ditetapkan ke (setara dengan path absolut dari ), yang merupakan sudah . Jadi kita perlu menghindari menambahkan untuk yang kedua kalinya.pathappend ./path/to/my/bin/dirARG..PATHARGA/path/to/my/bin/dir.PATH/path/to/my/bin/dirPATH

Mungkin yang lebih penting, tujuan utama readlinkadalah, seperti namanya, untuk melihat tautan simbolis dan membaca pathname yang dikandungnya (yaitu, menunjuk ke). Sebagai contoh:

$ 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

Sekarang, jika Anda berkata pathappend /usr/lib/perl/5.14, dan Anda sudah memiliki /usr/lib/perl/5.14di PATH Anda, baik, itu baik-baik saja; kita bisa membiarkannya apa adanya. Tapi, jika /usr/lib/perl/5.14tidak sudah di PATH Anda, kita sebut readlinkdan ARGA= /usr/lib/perl/5.14.2, dan kemudian kita menambahkan bahwa untuk PATH. Tapi tunggu sebentar - jika Anda sudah mengatakan pathappend /usr/lib/perl/5.14, maka Anda sudah memiliki /usr/lib/perl/5.14.2dalam PATH Anda, dan, sekali lagi, kita perlu memeriksa itu untuk menghindari menambahkannya ke PATHyang kedua kalinya.

3. Apa Kesepakatannya if ARGA=$(readlink -f "$ARG")?

Jika tidak jelas, baris ini menguji apakah readlinkberhasil. Ini hanya praktik pemrograman defensif yang bagus. Jika kita akan menggunakan output dari perintah  m sebagai bagian dari perintah  n (di mana m  <  n ), lebih baik untuk memeriksa apakah perintah  m gagal dan menanganinya dengan cara tertentu. Saya tidak berpikir kemungkinan itu readlinkakan gagal - tetapi, seperti yang dibahas dalam Cara mengambil jalur absolut file sewenang-wenang dari OS X dan di tempat lain, readlinkadalah penemuan GNU. Ini tidak ditentukan dalam POSIX, jadi ketersediaannya di Mac OS, Solaris, dan Unix non-Linux lainnya dipertanyakan. (Bahkan, saya baru saja membaca komentar yang mengatakan "readlink -ftampaknya tidak berfungsi di Mac OS X 10.11.6, tetapi realpathbekerja di luar kotak. ”Jadi, jika Anda menggunakan sistem yang tidak memiliki readlink, atau di mana readlink -ftidak berfungsi, Anda mungkin dapat memodifikasi ini skrip untuk digunakan realpath.) Dengan memasang jaring pengaman, kami membuat kode kami agak lebih portabel.

Tentu saja, jika Anda menggunakan sistem yang tidak memiliki readlink(atau  realpath), Anda tidak akan mau melakukannya .pathappend .

-dTes kedua ( [ -d "$ARGA" ]) mungkin benar-benar tidak perlu. Saya tidak bisa memikirkan skenario di mana $ARGdirektori dan readlinkberhasil, tetapi  $ARGAbukan direktori. Saya hanya menyalin dan menempelkan ifpernyataan pertama untuk membuat yang ketiga, dan saya meninggalkan  -dtes karena malas.

4. Ada Komentar Lain?

Ya. Seperti banyak jawaban lain di sini, yang ini menguji apakah setiap argumen adalah direktori, memprosesnya jika ada, dan mengabaikannya jika tidak. Ini mungkin (atau mungkin tidak) memadai jika Anda pathappend hanya menggunakan .file " " (seperti .bash_profiledan .bashrc) dan skrip lainnya. Tetapi, seperti yang ditunjukkan oleh jawaban ini (di atas), sangat layak untuk menggunakannya secara interaktif. Anda akan sangat bingung jika melakukannya

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

Apakah Anda memperhatikan bahwa saya mengatakan nysql dalam pathappendperintah, bukan mysql? Dan itu pathappendtidak mengatakan apa-apa; hanya diam-diam mengabaikan argumen yang salah?

Seperti yang saya katakan di atas, praktik yang baik untuk menangani kesalahan. Ini sebuah contoh:

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
}
qwertyzw
sumber
(1) Anda harus menambahkan tanda kutip: readlink -f "$ARG". (2) Saya tidak tahu mengapa itu terjadi (terutama setelah -d "$ARG"tes berhasil), tetapi Anda mungkin ingin menguji apakah readlinkgagal. (3) Anda tampaknya mengabaikan readlinkfungsi utama - untuk memetakan nama tautan simbolis ke "nama file asli". Jika (misalnya) /binadalah tautan simbolis ke /bin64, maka panggilan berulang pathappend /bindapat menghasilkan perkataan PATH …:/bin64:/bin64:/bin64:/bin64:…. Anda mungkin harus (juga) memeriksa apakah nilai baru $ARGsudah masuk PATH.
Scott
(1) Pengamatan yang baik, saya memperbaikinya. (2) dalam hal apa readlink akan gagal? Dengan asumsi bahwa jalur benar dan merujuk ke lokasi yang valid. (3) Saya tidak yakin apa yang menentukan fungsi utama readlink, saya percaya sebagian besar (jika tidak semua?) Jalur dalam sistem file unix dapat dibagi menjadi 2 jenis tautan, tautan simbolik dan tautan keras. Adapun entri jalur duplikat Anda benar, tetapi tujuan dari jawaban saya bukan untuk menambahkan itu (seperti yang saya perhatikan bahwa jawaban lain telah menyebutkannya). Satu-satunya alasan mengapa saya menambahkan jawaban lain adalah untuk menyumbangkan sesuatu yang saya pikir belum berkontribusi
qwertyzw
(2) Maksud saya, jika setidak-tidaknya nama perintah menyiratkan / mengisyaratkan tujuannya, maka 'tautan' di readlink dapat merujuk ke tautan keras atau lunak. Namun Anda benar: man readlink mengatakan 'readlink - cetak tautan simbolik yang diselesaikan atau nama file kanonik', .pada contoh saya, saya percaya dapat dianggap sebagai nama file kanonik. Benar?
qwertyzw
(1) Bagi orang yang memahami tautan simbolik, readlinknamanya jelas menyiratkan tujuannya - ia melihat tautan simbolik dan membacakan pathname yang dikandungnya (yaitu, menunjuk ke). (2) Ya, saya katakan bahwa saya tidak tahu mengapa readlinkgagal. Saya membuat poin umum bahwa jika skrip atau fungsi berisi banyak perintah, dan perintah  n dijamin gagal serempak (atau tidak masuk akal sama sekali) jika perintah  m gagal (di mana m  <  n ), merupakan praktik yang baik dan bijaksana untuk memeriksa apakah perintah m gagal dan menangani dalam beberapa cara - setidaknya, ... (Lanjutan)
Scott
(Lanjutan) ... batalkan skrip / fungsi dengan diagnostik. Hipotetis, readlinkbisa gagal jika (a) direktori diubah namanya atau dihapus (oleh proses lain) antara panggilan Anda ke testdan readlink, atau (b) jika /usr/bin/readlinkdihapus (atau rusak). (3) Anda sepertinya tidak mengerti maksud saya. Saya tidak mendorong Anda untuk menggandakan jawaban lain; Saya mengatakan bahwa, dengan memeriksa apakah dokumen asli ARG(dari baris perintah) sudah masuk PATH, tetapi tidak mengulangi pemeriksaan untuk yang baru ARG(keluaran dari readlink), jawaban Anda tidak lengkap dan karena itu salah. … (Lanjutan)
Scott
1
function __path_add(){  
if [ -d "$1" ] ; then  
    local D=":${PATH}:";   
    [ "${D/:$1:/:}" == "$D" ] && PATH="$PATH:$1";  
    PATH="${PATH/#:/}";  
    export PATH="${PATH/%:/}";  
fi  
}  
GreenFox
sumber
1

Cara ini berfungsi dengan baik:

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

Versi saya kurang hati-hati tentang jalur kosong dan bersikeras jalur yang valid dan direktori dari beberapa diposting di sini, tapi saya menemukan koleksi ish-ish dari prepend / append / clean / unique-ify / etc. fungsi shell berguna untuk manipulasi jalur. Seluruh lot, dalam keadaan saat ini, ada di sini: http://pastebin.com/xS9sgQsX (umpan balik dan perbaikan sangat disambut!)

andybuckley
sumber
0

Anda dapat menggunakan perl satu liner:

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;' "$@"
}

Ini dia di 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
}
dpatru
sumber
0

Saya sedikit mengubah jawaban Gordon Davisson untuk menggunakan dir saat ini jika tidak ada yang diberikan. Jadi Anda bisa melakukannya padddari direktori yang ingin Anda tambahkan ke PATH Anda.

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

Anda dapat memeriksa apakah variabel khusus telah disetel, jika tidak, atur kemudian tambahkan entri baru:

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

Tentu saja, entri ini masih dapat digandakan jika ditambahkan oleh skrip lain, seperti /etc/profile.

David Kennedy
sumber
0

Script ini memungkinkan Anda untuk menambahkan di akhir $PATH:

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

Atau tambahkan di awal $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
}
Tom Hale
sumber
0

Berikut adalah cara yang sesuai dengan 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"
        }
}

Hal ini cribbed dari Guillaume Perrault-Archambault 'jawaban s pertanyaan ini dan mike511 ' s jawaban di sini .

UPDATE 2017-11-23: Fixed bug per @Scott

go2null
sumber
Saya akan melakukan upvote untuk menyediakan opsi baris perintah untuk memilih antara prepending dan postpending (appending), dengan default. Tapi kemudian saya berpikir: ini kode yang agak samar tanpa penjelasan. (Dan fakta bahwa Anda memiliki dua fungsi, di mana yang satu mengubah PATH dan menggemakan nilainya yang baru, dan yang lainnya menangkap output itu dan menugaskannya ke PATH lagi , hanya kompleksitas yang tidak perlu.) ... (Lanjutan)
Scott
(Lanjutkan) ... Dan kemudian saya perhatikan bahwa tautannya salah. (Dan saya tidak yakin mengapa Anda menyalahkan orang-orang itu; Anda tampaknya tidak menyalin banyak dari jawaban mereka.) Dan kemudian saya perhatikan bahwa kodenya salah. Saya kira itu melakukan pekerjaan yang baik untuk mempertahankan PATH yang terbentuk dengan baik, tetapi tidak ada jaminan bahwa itu sudah terbentuk dengan baik, terutama jika Anda memiliki penerangan yang belum tercerahkan /etc/profile. Direktori yang Anda coba tambahkan ke PATH mungkin sudah ada lebih dari sekali , dan kode Anda tersendat karenanya. … (Lanjutan)
Scott
(Lanjutan) ... Sebagai contoh, jika Anda mencoba untuk tambahkan /a/ckke /b:/a/ck:/tr:/a/ck, Anda mendapatkan /a/ck:/b:/a/ck:/tr:/tr:/a/ck.
Scott