Apa cara paling elegan untuk menghapus jalur dari variabel $ PATH di Bash?

114

Atau secara lebih umum, bagaimana cara menghapus item dari daftar yang dipisahkan titik dua di variabel lingkungan Bash?

Saya pikir saya telah melihat cara sederhana untuk melakukannya bertahun-tahun yang lalu, menggunakan bentuk yang lebih maju dari ekspansi variabel Bash, tetapi jika demikian saya telah kehilangan jejaknya. Pencarian cepat Google ternyata hanya menemukan sedikit hasil yang relevan dan tidak ada yang saya sebut "sederhana" atau "elegan". Misalnya, dua metode menggunakan sed dan awk, masing-masing:

PATH=$(echo $PATH | sed -e 's;:\?/home/user/bin;;' -e 's;/home/user/bin:\?;;')
PATH=!(awk -F: '{for(i=1;i<=NF;i++){if(!($i in a)){a[$i];printf s$i;s=":"}}}'<<<$PATH)

Apakah tidak ada yang langsung? Apakah ada analogi dengan fungsi split () di Bash?

Pembaruan:
Sepertinya saya perlu meminta maaf atas pertanyaan saya yang sengaja tidak jelas; Saya kurang tertarik untuk menyelesaikan kasus penggunaan tertentu daripada memancing diskusi yang baik. Untungnya, saya mengerti!

Ada beberapa teknik yang sangat pintar di sini. Pada akhirnya, saya telah menambahkan tiga fungsi berikut ke kotak peralatan saya. Keajaiban terjadi di path_remove, yang sebagian besar didasarkan pada penggunaan awkvariabel RS yang cerdas oleh Martin York .

path_append ()  { path_remove $1; export PATH="$PATH:$1"; }
path_prepend () { path_remove $1; export PATH="$1:$PATH"; }
path_remove ()  { export PATH=`echo -n $PATH | awk -v RS=: -v ORS=: '$0 != "'$1'"' | sed 's/:$//'`; }

Satu-satunya masalah nyata di sana adalah penggunaan seduntuk menghilangkan tanda titik dua. Mengingat betapa mudahnya solusi Martin lainnya, saya cukup bersedia menerimanya!


Pertanyaan terkait: Bagaimana cara memanipulasi elemen $ PATH dalam skrip shell?

Ben Blank
sumber
Untuk variabel apa pun: WORK=`echo -n ${1} | awk -v RS=: -v ORS=: '$0 != "'${3}'"' | sed 's/:$//'`; eval "export ${2}=${WORK}"tetapi Anda harus menyebutnya sebagai func $VAR VAR pattern(berdasarkan @ martin-york dan @ andrew-aylett)
vesperto

Jawaban:

51

Satu menit dengan awk:

# Strip all paths with SDE in them.
#
export PATH=`echo ${PATH} | awk -v RS=: -v ORS=: '/SDE/ {next} {print}'`

Edit: Ini menanggapi komentar di bawah ini:

$ export a="/a/b/c/d/e:/a/b/c/d/g/k/i:/a/b/c/d/f:/a/b/c/g:/a/b/c/d/g/i"
$ echo ${a}
/a/b/c/d/e:/a/b/c/d/f:/a/b/c/g:/a/b/c/d/g/i

## Remove multiple (any directory with a: all of them)
$ echo ${a} | awk -v RS=: -v ORS=: '/a/ {next} {print}'
## Works fine all removed

## Remove multiple including last two: (any directory with g)
$ echo ${a} | awk -v RS=: -v ORS=: '/g/ {next} {print}'
/a/b/c/d/e:/a/b/c/d/f:
## Works fine: Again!

Edit sebagai jawaban atas masalah keamanan: (yang tidak relevan dengan pertanyaan)

export PATH=$(echo ${PATH} | awk -v RS=: -v ORS=: '/SDE/ {next} {print}' | sed 's/:*$//')

Ini menghapus titik dua tertinggal dengan menghapus entri terakhir, yang secara efektif akan menambah .jalur Anda.

Martin York
sumber
1
Gagal saat mencoba menghapus elemen terakhir atau beberapa elemen: dalam kasus pertama, ia menambahkan dir saat ini (sebagai string kosong; lubang keamanan potensial), dalam kasus kedua ia menambahkan `` sebagai elemen jalur.
Fred Foo
1
@ Larsmans: Bekerja dengan baik untuk saya. Catatan: Kosong tidak sama dengan direktori saat ini yaitu "./"
Martin York
2
String kosong sebagai "anggota" dari PATHvariabel tidak , sebagai aturan khusus, masing menunjukkan direktori saat ini di semua kerang Unix setidaknya sejak V7 Unix dari tahun 1979. Ini masih tidak dalam bash. Periksa manual atau coba sendiri.
Fred Foo
1
@Martin: POSIX tidak memerlukan perilaku ini, tetapi mendokumentasikan dan mengizinkannya: pubs.opengroup.org/onlinepubs/9699919799/basedefs/…
Fred Foo
1
Ada masalah yang lebih halus lagi saat menghapus elemen terakhir dengan ini: awk tampaknya menambahkan byte nol ke akhir string . Artinya jika nanti Anda menambahkan direktori lain ke PATH, sebenarnya tidak akan dicari.
sschuberth
55

Retasan kotor saya:

echo ${PATH} > t1
vi t1
export PATH=$(cat t1)
Martin York
sumber
18
Tidak pernah pertanda baik ketika solusi yang paling jelas adalah untuk de -automate proses. :-D
Ben Blank
JAUH lebih aman daripada alternatif yang "elegan".
Jortstek
44

Karena masalah besar substitusi adalah kasus akhir, bagaimana kalau kasus akhir tidak berbeda dengan kasus lainnya? Jika jalur sudah memiliki titik dua di awal dan akhir, kita cukup mencari string yang diinginkan yang dibungkus dengan titik dua. Karena adanya, kita dapat dengan mudah menambahkan titik dua tersebut dan menghapusnya sesudahnya.

# PATH => /bin:/opt/a dir/bin:/sbin
WORK=:$PATH:
# WORK => :/bin:/opt/a dir/bin:/sbin:
REMOVE='/opt/a dir/bin'
WORK=${WORK/:$REMOVE:/:}
# WORK => :/bin:/sbin:
WORK=${WORK%:}
WORK=${WORK#:}
PATH=$WORK
# PATH => /bin:/sbin

Pesta murni :).

Andrew Aylett
sumber
2
Saya akan menambahkan bagian tutorial ini untuk beberapa frosting tambahan: tldp.org/LDP/abs/html/string-manipulation.html
Cyber ​​Oliveira
1
Saya menggunakan ini karena sepertinya solusi paling sederhana. Itu super cepat dan mudah, dan Anda dapat dengan mudah memeriksa pekerjaan Anda dengan echo $ WORK tepat sebelum baris terakhir di mana Anda benar-benar mengubah variabel PATH.
Phil Gran
2
Benar-benar permata kecil. Persis apa yang saya coba lakukan ketika saya menemukan posting ini. -Terima kasih Andrew! BTW: Mungkin Anda ingin menambahkan tanda kutip ganda di sekitar ": $ PATH:", kalau-kalau itu harus berisi spasi (sama tentang "/ usr / bin") dan baris terakhir "$ WORK".
2
Terima kasih, @ PacMan-- :). Saya cukup yakin (baru mencobanya) Anda tidak memerlukan spasi untuk tugas WORKdan PATHkarena ekspansi variabel terjadi setelah baris diuraikan menjadi beberapa bagian untuk tugas variabel dan eksekusi perintah. REMOVEmungkin perlu dikutip, atau Anda bisa langsung memasukkan string Anda ke penggantinya jika itu adalah konstanta.
Andrew Aylett
Saya selalu bingung ketika string dipertahankan, jadi saya mulai selalu mengutip string ganda. Sekali lagi terima kasih telah menjelaskan ini. :)
26

Inilah solusi paling sederhana yang bisa saya buat:

#!/bin/bash
IFS=:
# convert it to an array
t=($PATH)
unset IFS
# perform any array operations to remove elements from the array
t=(${t[@]%%*usr*})
IFS=:
# output the new array
echo "${t[*]}"

Contoh di atas akan menghapus elemen apa pun di $ PATH yang berisi "usr". Anda dapat mengganti "* usr *" dengan "/ home / user / bin" untuk menghapus elemen itu saja.

perbarui per sschuberth

Meskipun menurut saya spasi di a $PATHadalah ide yang buruk , berikut adalah solusi yang menanganinya:

PATH=$(IFS=':';t=($PATH);n=${#t[*]};a=();for ((i=0;i<n;i++)); do p="${t[i]%%*usr*}"; [ "${p}" ] && a[i]="${p}"; done;echo "${a[*]}");

atau

IFS=':'
t=($PATH)
n=${#t[*]}
a=()
for ((i=0;i<n;i++)); do
  p="${t[i]%%*usr*}"
  [ "${p}" ] && a[i]="${p}"
done
echo "${a[*]}"
nicerobot
sumber
2
Sebagai satu liner: PATH = $ (IFS = ':'; t = ($ PATH); unset IFS; t = ($ {t [@] %% * usr *}); IFS = ':'; echo "$ {t [*]} ");
nicerobot
1
Solusi ini tidak bekerja dengan jalur di PATH yang berisi spasi; itu menggantikan mereka dengan titik dua.
sschuberth
11

Berikut ini satu baris yang, meskipun jawaban yang diterima dan nilai tertinggi saat ini , tidak menambahkan karakter yang tidak terlihat ke PATH dan dapat mengatasi jalur yang berisi spasi:

export PATH=$(p=$(echo $PATH | tr ":" "\n" | grep -v "/cygwin/" | tr "\n" ":"); echo ${p%:})

Secara pribadi, saya juga merasa ini mudah dibaca / dipahami, dan ini hanya melibatkan perintah umum daripada menggunakan awk.

sschuberth.dll
sumber
2
... dan jika Anda menginginkan sesuatu yang dapat mengatasi bahkan dengan baris baru dalam nama file, Anda dapat menggunakan ini: export PATH=$(p=$(echo $PATH | tr ":" "\0" | grep -v -z "/cygwin/" | tr "\0" ":"); echo ${p%:}) (meskipun bisa dibilang, Anda mungkin ingin bertanya pada diri sendiri mengapa Anda membutuhkan ini, jika Anda melakukannya :))
ehdr
Ini akan menghapus kecocokan parsial, yang mungkin bukan yang Anda inginkan; Saya akan menggunakan grep -v "^/path/to/remove\$"ataugrep -v -x "/path/to/remove"
ShadSterling
Solusi bagus, tetapi apakah menurut Anda trlebih umum daripada awk? ;)
K.-Michael Aye
1
Benar. Lingkungan ringan, seperti Git Bash di Windows, lebih datang dengan alat sederhana seperti trdaripada penerjemah awk.
sschuberth
1
@ehdr: Anda perlu mengganti echo "..."dengan printf "%s" "..."untuk itu untuk bekerja pada jalur seperti -edan serupa. Lihat stackoverflow.com/a/49418406/102441
Eric
8

Berikut adalah solusi yang:

  • adalah Bash murni,
  • tidak memanggil proses lain (seperti 'sed' atau 'awk'),
  • tidak berubah IFS,
  • tidak membagi sub-shell,
  • menangani jalur dengan spasi, dan
  • menghapus semua kemunculan argumen di PATH.

    removeFromPath () {
       pd lokal
       p = ": $ 1:"
       d = ": $ PATH:"
       d = $ {h // $ p /:}
       d = $ {h / #: /}
       JALUR = $ {d /%: /}
    }
robinbb
sumber
4
Saya suka solusi ini. Mungkin membuat nama variabel lebih deskriptif?
Anukool
sangat bagus. Namun, tidak berfungsi untuk segmen jalur yang berisi tanda bintang (*). Terkadang mereka sampai di sana secara tidak sengaja.
Jörg
6

function __path_remove () {
lokal D = ": $ {PATH}:";
["$ {D /: $ 1: /:}"! = "$ D"] && PATH = "$ {D /: $ 1: /:}";
PATH = "$ {PATH / #: /}";
ekspor PATH = "$ {PATH /%: /}";
}

Gali dari file .bashrc saya. Saat Anda bermain-main dengan PATH, dan hilang, awk / sed / grep tidak tersedia :-)

GreenFox
sumber
1
Itu poin yang sangat bagus. (Saya tidak pernah suka menjalankan utilitas eksternal untuk hal-hal sederhana seperti ini).
6

Opsi bash murni terbaik yang saya temukan sejauh ini adalah sebagai berikut:

function path_remove {
  PATH=${PATH/":$1"/} # delete any instances in the middle or at the end
  PATH=${PATH/"$1:"/} # delete any instances at the beginning
}

Ini didasarkan pada jawaban yang kurang tepat untuk Tambahkan direktori ke $ PATH jika belum ada di Superuser.

Mark Booth
sumber
Ini juga cukup bagus. Saya mengujinya. Jika ada jalur duplikat (mis. Dua jalur yang persis sama) di PATH, maka hanya satu yang dihapus. Anda juga dapat membuatnya menjadi satu baris:removePath () { PATH=${PATH/":$1"/}; PATH=${PATH/"$1:"/}; }
Solusi ini gagal saat $PATHberisi sub-folder dari jalur target (yaitu akan dihapus). Misalnya: a:abc/def/bin:b-> a/bin:b, kapan abc/defakan dihapus.
Robin Hsu
5

Saya baru saja menggunakan fungsi-fungsi dalam distribusi bash, yang tampaknya sudah ada sejak 1991. Fungsi-fungsi ini masih ada dalam paket bash-docs di Fedora, dan pernah digunakan di /etc/profile, tetapi tidak lebih ...

$ rpm -ql bash-doc |grep pathfunc
/usr/share/doc/bash-4.2.20/examples/functions/pathfuncs
$ cat $(!!)
cat $(rpm -ql bash-doc |grep pathfunc)
#From: "Simon J. Gerraty" <[email protected]>
#Message-Id: <[email protected]>
#Subject: Re: a shell idea?
#Date: Mon, 09 Oct 1995 21:30:20 +1000


# NAME:
#       add_path.sh - add dir to path
#
# DESCRIPTION:
#       These functions originated in /etc/profile and ksh.kshrc, but
#       are more useful in a separate file.
#
# SEE ALSO:
#       /etc/profile
#
# AUTHOR:
#       Simon J. Gerraty <[email protected]>

#       @(#)Copyright (c) 1991 Simon J. Gerraty
#
#       This file is provided in the hope that it will
#       be of use.  There is absolutely NO WARRANTY.
#       Permission to copy, redistribute or otherwise
#       use this file is hereby granted provided that
#       the above copyright notice and this notice are
#       left intact.

# is $1 missing from $2 (or PATH) ?
no_path() {
        eval "case :\$${2-PATH}: in *:$1:*) return 1;; *) return 0;; esac"
}
# if $1 exists and is not in path, append it
add_path () {
  [ -d ${1:-.} ] && no_path $* && eval ${2:-PATH}="\$${2:-PATH}:$1"
}
# if $1 exists and is not in path, prepend it
pre_path () {
  [ -d ${1:-.} ] && no_path $* && eval ${2:-PATH}="$1:\$${2:-PATH}"
}
# if $1 is in path, remove it
del_path () {
  no_path $* || eval ${2:-PATH}=`eval echo :'$'${2:-PATH}: |
    sed -e "s;:$1:;:;g" -e "s;^:;;" -e "s;:\$;;"`
}
Tn. Aneh
sumber
4

Saya memang menulis jawaban untuk ini di sini (menggunakan awk juga). Tapi saya tidak yakin itu yang Anda cari? Setidaknya terlihat jelas bagi saya apa yang dilakukannya, alih-alih mencoba masuk ke dalam satu baris. Untuk satu liner sederhana, itu hanya menghapus barang, saya sarankan

echo $PATH | tr ':' '\n' | awk '$0 != "/bin"' | paste -sd:

Mengganti adalah

echo $PATH | tr ':' '\n' | 
    awk '$0 != "/bin"; $0 == "/bin" { print "/bar" }' | paste -sd:

atau (lebih pendek tapi kurang terbaca)

echo $PATH | tr ':' '\n' | awk '$0 == "/bin" { print "/bar"; next } 1' | paste -sd:

Bagaimanapun, untuk pertanyaan yang sama, dan banyak jawaban berguna, lihat di sini .

Johannes Schaub - litb
sumber
Dan jika Anda ingin menghapus garis yang berisi string parsial, gunakan awk '$0 !~ "/bin"'. Yaitu menjaga baris yang tidak mengandung '/ bin' dengan operator awk !~.
thoni56
3

Nah, dalam bash, karena mendukung ekspresi reguler, saya hanya akan melakukan:

PATH=${PATH/:\/home\/user\/bin/}
tikar
sumber
Bukankah itu hanya perluasan nama jalur, bukan ekspresi reguler?
dreamlax
2
Meskipun bash mendukung ekspresi reguler (seperti pada bash 3), ini bukan contohnya, ini adalah substitusi variabel.
Robert Gamble
4
Ini adalah ekspansi variabel pola dan solusinya memiliki beberapa masalah. 1) tidak akan cocok dengan elemen pertama. 2) itu akan cocok dengan apapun yang dimulai dengan "/ home / user / bin", bukan hanya "/ home / user / bin". 3) itu membutuhkan pelarian karakter khusus. Paling banter, menurut saya ini adalah contoh yang tidak lengkap.
nicerobot
2

Saya suka tiga fungsi yang ditunjukkan dalam pembaruan @ BenBlank untuk pertanyaan aslinya. Untuk menggeneralisasikannya, saya menggunakan formulir 2-argumen, yang memungkinkan saya menyetel PATH atau variabel lingkungan lain yang saya inginkan:

path_append ()  { path_remove $1 $2; export $1="${!1}:$2"; }
path_prepend () { path_remove $1 $2; export $1="$2:${!1}"; }
path_remove ()  { export $1="`echo -n ${!1} | awk -v RS=: -v ORS=: '$1 != "'$2'"' | sed 's/:$//'`"; }

Contoh penggunaan:

path_prepend PATH /usr/local/bin
path_append PERL5LIB "$DEVELOPMENT_HOME/p5/src/perlmods"

Perhatikan bahwa saya juga menambahkan beberapa tanda kutip untuk memungkinkan pemrosesan nama jalur yang mengandung spasi.

Cary Millsap
sumber
2

Apa cara paling elegan untuk menghapus jalur dari variabel $ PATH di Bash?

Apa yang lebih elegan dari awk?

path_remove ()  { export PATH=`echo -n $PATH | awk -v RS=: -v ORS=: '$0 != "'$1'"' | sed 's/:$//'`; 

Python! Ini adalah solusi yang lebih mudah dibaca dan dipelihara, dan mudah untuk diperiksa untuk melihat bahwa itu benar-benar melakukan apa yang Anda inginkan.

Katakanlah Anda ingin menghapus elemen jalur pertama?

PATH="$(echo "$PATH" | python -c "import sys; path = sys.stdin.read().split(':'); del path[0]; print(':'.join(path))")"

(Alih-alih menyalurkan dari echo, os.getenv['PATH']akan sedikit lebih pendek, dan memberikan hasil yang sama seperti di atas, tetapi saya khawatir Python mungkin melakukan sesuatu dengan variabel lingkungan itu, jadi mungkin yang terbaik adalah menyalurkannya langsung dari lingkungan yang Anda pedulikan. .)

Demikian pula untuk menghapus dari akhir:

PATH="$(echo "$PATH" | python -c "import sys; path = sys.stdin.read().split(':'); del path[-1]; print(':'.join(path))")"

Untuk membuat fungsi shell yang dapat digunakan kembali ini, Anda dapat, misalnya, tetap menggunakan file .bashrc Anda:

strip_path_first () {
    PATH="$(echo "$PATH" | 
    python -c "import sys; path = sys.stdin.read().split(':'); del path[0]; print(':'.join(path))")"
}

strip_path_last () {
    PATH="$(echo "$PATH" | 
    python -c "import sys; path = sys.stdin.read().split(':'); del path[-1]; print(':'.join(path))")"
}
Aaron Hall
sumber
1

Ya, menempatkan titik dua di akhir PATH, misalnya, membuat penghapusan jalur menjadi tidak terlalu kikuk & rawan kesalahan.

path_remove ()  { 
   declare i newPATH
   newPATH="${PATH}:"
   for ((i=1; i<=${#@}; i++ )); do
      #echo ${@:${i}:1}
      newPATH="${newPATH//${@:${i}:1}:/}" 
   done
   export PATH="${newPATH%:}" 
   return 0; 
} 

path_remove_all ()  {
   declare i newPATH
   shopt -s extglob
   newPATH="${PATH}:"
   for ((i=1; i<=${#@}; i++ )); do
      newPATH="${newPATH//+(${@:${i}:1})*([^:]):/}" 
      #newPATH="${newPATH//+(${@:${i}:1})*([^:])+(:)/}" 
   done
   shopt -u extglob 
   export PATH="${newPATH%:}" 
   return 0 
} 

path_remove /opt/local/bin /usr/local/bin

path_remove_all /opt/local /usr/local 
cyrill
sumber
1

Jika Anda khawatir tentang menghapus duplikat di $ PATH, cara paling elegan, IMHO, adalah dengan tidak menambahkannya sejak awal. Dalam 1 baris:

if ! $( echo "$PATH" | tr ":" "\n" | grep -qx "$folder" ) ; then PATH=$PATH:$folder ; fi

$ folder dapat diganti dengan apa saja, dan mungkin berisi spasi ("/ home / user / my documents")

MestreLion
sumber
1

Solusi pesta murni paling elegan yang pernah saya temukan hingga saat ini:

pathrm () {                                                                      
  local IFS=':'                                                                  
  local newpath                                                                  
  local dir                                                                      
  local pathvar=${2:-PATH}                                                       
  for dir in ${!pathvar} ; do                                                    
    if [ "$dir" != "$1" ] ; then                                                 
      newpath=${newpath:+$newpath:}$dir                                          
    fi                                                                           
  done                                                                           
  export $pathvar="$newpath"                                                        
}

pathprepend () {                                                                 
  pathrm $1 $2                                                                   
  local pathvar=${2:-PATH}                                                       
  export $pathvar="$1${!pathvar:+:${!pathvar}}"                                  
}

pathappend () {                                                                    
  pathrm $1 $2                                                                   
  local pathvar=${2:-PATH}                                                       
  export $pathvar="${!pathvar:+${!pathvar}:}$1"                                  
} 
TriangleTodd
sumber
1

Sebagian besar solusi yang disarankan lainnya hanya bergantung pada string matching dan tidak memperhitungkan segmen jalan yang berisi nama-nama khusus seperti ., .., atau ~. Fungsi bash di bawah ini menyelesaikan string direktori dalam argumennya dan di segmen jalur untuk menemukan direktori logis yang cocok serta string yang cocok.

rm_from_path() {
  pattern="${1}"
  dir=''
  [ -d "${pattern}" ] && dir="$(cd ${pattern} && pwd)"  # resolve to absolute path

  new_path=''
  IFS0=${IFS}
  IFS=':'
  for segment in ${PATH}; do
    if [[ ${segment} == ${pattern} ]]; then             # string match
      continue
    elif [[ -n ${dir} && -d ${segment} ]]; then
      segment="$(cd ${segment} && pwd)"                 # resolve to absolute path
      if [[ ${segment} == ${dir} ]]; then               # logical directory match
        continue
      fi
    fi
    new_path="${new_path}${IFS}${segment}"
  done
  new_path="${new_path/#${IFS}/}"                       # remove leading colon, if any
  IFS=${IFS0}

  export PATH=${new_path}
}

Uji:

$ mkdir -p ~/foo/bar/baz ~/foo/bar/bif ~/foo/boo/bang
$ PATH0=${PATH}
$ PATH=~/foo/bar/baz/.././../boo/././../bar:${PATH}  # add dir with special names
$ rm_from_path ~/foo/boo/../bar/.  # remove same dir with different special names
$ [ ${PATH} == ${PATH0} ] && echo 'PASS' || echo 'FAIL'
jwfearn
sumber
ditambah satu untuk di luar kotak
Martin York
1

Linux dari Scratch mendefinisikan tiga fungsi Bash di /etc/profile:

# Functions to help us manage paths.  Second argument is the name of the
# path variable to be modified (default: PATH)
pathremove () {
        local IFS=':'
        local NEWPATH
        local DIR
        local PATHVARIABLE=${2:-PATH}
        for DIR in ${!PATHVARIABLE} ; do
                if [ "$DIR" != "$1" ] ; then
                  NEWPATH=${NEWPATH:+$NEWPATH:}$DIR
                fi
        done
        export $PATHVARIABLE="$NEWPATH"
}

pathprepend () {
        pathremove $1 $2
        local PATHVARIABLE=${2:-PATH}
        export $PATHVARIABLE="$1${!PATHVARIABLE:+:${!PATHVARIABLE}}"
}

pathappend () {
        pathremove $1 $2
        local PATHVARIABLE=${2:-PATH}
        export $PATHVARIABLE="${!PATHVARIABLE:+${!PATHVARIABLE}:}$1"
}

export -f pathremove pathprepend pathappend

Ref: http://www.linuxfromscratch.org/blfs/view/svn/postlfs/profile.html

kevinarpe
sumber
1

Saya tahu pertanyaan ini menanyakan tentang BASH, yang seharusnya disukai semua orang, tetapi karena saya menikmati simetri dan terkadang saya diharuskan menggunakan "csh", saya membuat yang setara dengan "path_prepend ()", "path_append ()" dan "path_remove () "solusi elegan di atas.

Intinya adalah bahwa "csh" tidak memiliki fungsi, jadi saya meletakkan skrip shell kecil di direktori bin pribadi saya yang berfungsi seperti fungsi. Saya membuat alias untuk SUMBER skrip tersebut untuk membuat perubahan variabel lingkungan yang ditunjuk.

~ / bin / _path_remove.csh:

set _resolve = `eval echo $2`
setenv $1 `eval echo -n \$$1 | awk -v RS=: -v ORS=: '$1 != "'${_resolve}'"' | sed 's/:$//'`;
unset _resolve

~ / bin / _path_append.csh:

source ~/bin/_path_remove.csh $1 $2
set _base = `eval echo \$$1`
set _resolve = `eval echo $2`
setenv $1 ${_base}:${_resolve}
unset _base _resolve

~ / bin / _path_prepend.csh:

source ~/bin/_path_remove.csh $1 $2
set _base = `eval echo \$$1`
set _resolve = `eval echo $2`
setenv $1 ${_resolve}:${_base}
unset _base _resolve

~ / bin / .cshrc:


alias path_remove  "source ~/bin/_path_remove.csh  '\!:1' '\!:2'"
alias path_append  "source ~/bin/_path_append.csh  '\!:1' '\!:2'"
alias path_prepend "source ~/bin/_path_prepend.csh '\!:1' '\!:2'"

Anda dapat menggunakannya seperti ini ...

%(csh)> path_append MODULEPATH ${HOME}/modulefiles
Lance ET Compte
sumber
0

Karena ini cenderung cukup bermasalah, karena TIDAK ADA cara yang elegan, saya sarankan untuk menghindari masalah dengan mengatur ulang solusinya: bangun PATH Anda daripada mencoba menghancurkannya.

Saya bisa lebih spesifik jika saya tahu konteks masalah Anda yang sebenarnya. Untuk sementara, saya akan menggunakan software build sebagai konteksnya.

Masalah umum dengan build perangkat lunak adalah kerusakan pada beberapa mesin, yang pada akhirnya disebabkan oleh cara seseorang mengonfigurasi shell default mereka (PATH dan variabel lingkungan lainnya). Solusi elegannya adalah membuat skrip build Anda kebal dengan menentukan lingkungan shell sepenuhnya. Kode skrip build Anda untuk menyetel PATH dan variabel lingkungan lainnya berdasarkan bagian assembling yang Anda kontrol, seperti lokasi compiler, library, alat, komponen, dll. Jadikan setiap item yang dapat dikonfigurasi sesuatu yang dapat Anda setel, verifikasi, dan kemudian gunakan dengan tepat dalam naskah Anda.

Misalnya, saya memiliki build Java bertarget WebLogic berbasis Maven yang saya warisi di perusahaan baru saya. Skrip build terkenal sangat rapuh, dan saya dan karyawan baru lainnya menghabiskan waktu tiga minggu (bukan penuh waktu, hanya di sana-sini, tetapi masih berjam-jam) untuk membuatnya berfungsi di mesin kami. Langkah penting adalah saya mengambil kendali PATH sehingga saya tahu persis Java mana, Maven mana, dan WebLogic mana yang sedang dipanggil. Saya membuat variabel lingkungan untuk menunjuk ke masing-masing alat tersebut, lalu saya menghitung PATH berdasarkan alat tersebut ditambah beberapa lainnya. Teknik serupa menjinakkan pengaturan lain yang dapat dikonfigurasi, sampai kami akhirnya membuat build yang dapat direproduksi.

Ngomong-ngomong, jangan gunakan Maven, Java tidak apa-apa, dan hanya beli WebLogic jika Anda benar-benar membutuhkan pengelompokannya (tetapi sebaliknya tidak, dan terutama fitur kepemilikannya).

Semoga sukses.

Rob Williams
sumber
terkadang Anda tidak memiliki akses root dan admin Anda yang mengelola PATH. Tentu, Anda dapat membuat sendiri, tetapi setiap kali admin Anda memindahkan sesuatu, Anda harus mencari tahu di mana dia meletakkannya. Semacam itu mengalahkan tujuan memiliki seorang admin.
Shep
0

Seperti @litb, saya menyumbangkan jawaban untuk pertanyaan " Bagaimana cara memanipulasi elemen $ PATH dalam skrip shell ", jadi jawaban utama saya ada di sana.

Fungsionalitas 'split' dalam bashdan turunan Bourne shell lainnya paling baik dicapai dengan $IFS, pemisah antar-bidang. Misalnya, untuk mengatur argumen posisional ( $1, $2, ...) dengan unsur-unsur dari PATH, gunakan:

set -- $(IFS=":"; echo "$PATH")

Ini akan bekerja dengan baik selama tidak ada spasi di $ PATH. Membuatnya berfungsi untuk elemen jalur yang berisi spasi adalah latihan yang tidak sepele - diserahkan kepada pembaca yang tertarik. Mungkin lebih mudah untuk mengatasinya dengan menggunakan bahasa scripting seperti Perl.

Saya juga memiliki skrip clnpath, yang saya gunakan secara ekstensif untuk menyetel PATH saya. Saya mendokumentasikannya dalam jawaban untuk " Bagaimana agar tidak menduplikasi variabel PATH di csh ".

Jonathan Leffler
sumber
IFS =: a = ($ PATH); IFS = pemisahan juga bagus. berfungsi jika berisi spasi juga. tapi kemudian Anda mendapatkan sebuah array, dan harus bermain-main dengan for loop dan semacamnya untuk menghapus nama.
Johannes Schaub - litb
Iya; itu menjadi fiddly - seperti dengan komentar saya yang diperbarui, mungkin lebih sederhana untuk menggunakan bahasa scripting pada saat ini.
Jonathan Leffler
0

Apa yang membuat masalah ini mengganggu adalah kasus tiang pagar di antara elemen pertama dan terakhir. Masalahnya dapat diselesaikan dengan elegan dengan mengubah IFS dan menggunakan array, tetapi saya tidak tahu cara memasukkan kembali titik dua setelah jalur diubah ke bentuk array.

Berikut adalah versi yang sedikit kurang elegan yang menghapus satu direktori dari hanya $PATHmenggunakan manipulasi string. Saya sudah mengujinya.

#!/bin/bash
#
#   remove_from_path dirname
#
#   removes $1 from user's $PATH

if [ $# -ne 1 ]; then
  echo "Usage: $0 pathname" 1>&2; exit 1;
fi

delendum="$1"
NEWPATH=
xxx="$IFS"
IFS=":"
for i in $PATH ; do
  IFS="$xxx"
  case "$i" in
    "$delendum") ;; # do nothing
    *) [ -z "$NEWPATH" ] && NEWPATH="$i" || NEWPATH="$NEWPATH:$i" ;;
  esac
done

PATH="$NEWPATH"
echo "$PATH"
Norman Ramsey
sumber
0

Inilah Perl one-liner:

PATH=`perl -e '$a=shift;$_=$ENV{PATH};s#:$a(:)|^$a:|:$a$#$1#;print' /home/usr/bin`

The $avariabel mendapat jalan untuk dihapus. Perintah s(substitusi) dan printsecara implisit beroperasi pada $_variabel.

JA Faucett
sumber
0

Barang bagus di sini. Saya menggunakan yang satu ini untuk menjaga agar tidak menambahkan penipuan di tempat pertama.

#!/bin/bash
#
######################################################################################
#
# Allows a list of additions to PATH with no dupes
# 
# Patch code below into your $HOME/.bashrc file or where it
# will be seen at login.
#
# Can also be made executable and run as-is.
#
######################################################################################

# add2path=($HOME/bin .)                  ## uncomment space separated list 
if [ $add2path ]; then                    ## skip if list empty or commented out
for nodup in ${add2path[*]}
do
    case $PATH in                 ## case block thanks to MIKE511
    $nodup:* | *:$nodup:* | *:$nodup ) ;;    ## if found, do nothing
    *) PATH=$PATH:$nodup          ## else, add it to end of PATH or
    esac                          ## *) PATH=$nodup:$PATH   prepend to front
done
export PATH
fi
## debug add2path
echo
echo " PATH == $PATH"
echo
ongoto
sumber
1
Anda dapat menyederhanakan pernyataan kasus Anda dengan menambahkan titik dua di depan dan di belakangnya ke string PATH:case ":$PATH:" in (*:"$nodup":*) ;; (*) PATH="$PATH:$nodup" ;; esac
glenn jackman
0

Dengan mengaktifkan perluasan globbing, Anda dapat melakukan hal berikut:

# delete all /opt/local paths in PATH
shopt -s extglob 
printf "%s\n" "${PATH}" | tr ':' '\n' | nl
printf "%s\n" "${PATH//+(\/opt\/local\/)+([^:])?(:)/}" | tr ':' '\n' | nl 

man bash | less -p extglob
carlo
sumber
0

Globbing satu baris yang diperluas (yah, semacam):

path_remove ()  { shopt -s extglob; PATH="${PATH//+(${1})+([^:])?(:)/}"; export PATH="${PATH%:}"; shopt -u extglob; return 0; } 

Tampaknya tidak perlu menghindari garis miring di $ 1.

path_remove ()  { shopt -s extglob; declare escArg="${1//\//\\/}"; PATH="${PATH//+(${escArg})+([^:])?(:)/}"; export PATH="${PATH%:}"; shopt -u extglob; return 0; } 
carlo
sumber
0

Menambahkan titik dua ke PATH kita juga bisa melakukan sesuatu seperti:

path_remove ()  { 
   declare i newPATH
   # put a colon at the beginning & end AND double each colon in-between
   newPATH=":${PATH//:/::}:"   
   for ((i=1; i<=${#@}; i++)); do
       #echo ${@:${i}:1}
       newPATH="${newPATH//:${@:${i}:1}:/}"   # s/:\/fullpath://g
   done
   newPATH="${newPATH//::/:}"
   newPATH="${newPATH#:}"      # remove leading colon
   newPATH="${newPATH%:}"      # remove trailing colon
   unset PATH 
   PATH="${newPATH}" 
   export PATH
   return 0 
} 


path_remove_all ()  {
   declare i newPATH extglobVar
   extglobVar=0
   # enable extended globbing if necessary
   [[ ! $(shopt -q extglob) ]]  && { shopt -s extglob; extglobVar=1; }
   newPATH=":${PATH}:"
   for ((i=1; i<=${#@}; i++ )); do
      newPATH="${newPATH//:+(${@:${i}:1})*([^:])/}"     # s/:\/path[^:]*//g
   done
   newPATH="${newPATH#:}"      # remove leading colon
   newPATH="${newPATH%:}"      # remove trailing colon
   # disable extended globbing if it was enabled in this function
   [[ $extglobVar -eq 1 ]] && shopt -u extglob
   unset PATH 
   PATH="${newPATH}" 
   export PATH
   return 0 
} 

path_remove /opt/local/bin /usr/local/bin

path_remove_all /opt/local /usr/local 
proxy
sumber
0

Di path_remove_all (oleh proxxy):

-newPATH="${newPATH//:+(${@:${i}:1})*([^:])/}" 
+newPATH="${newPATH//:${@:${i}:1}*([^:])/}"        # s/:\/path[^:]*//g 
marius
sumber
0

Meskipun ini adalah utas yang sangat lama, saya pikir solusi ini mungkin menarik:

PATH="/usr/lib/ccache:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games"
REMOVE="ccache" # whole or part of a path :)
export PATH=$(IFS=':';p=($PATH);unset IFS;p=(${p[@]%%$REMOVE});IFS=':';echo "${p[*]}";unset IFS)
echo $PATH # outputs /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games

menemukannya di entri blog ini . Saya rasa saya paling suka yang ini :)

mjc
sumber
0

Saya mengambil pendekatan yang sedikit berbeda dari kebanyakan orang di sini dan berfokus secara khusus pada manipulasi string saja, seperti:

path_remove () {
    if [[ ":$PATH:" == *":$1:"* ]]; then
        local dirs=":$PATH:"
        dirs=${dirs/:$1:/:}
        export PATH="$(__path_clean $dirs)"
    fi
}
__path_clean () {
    local dirs=${1%?}
    echo ${dirs#?}
}

Di atas adalah contoh sederhana dari fungsi terakhir yang saya gunakan. Saya juga telah membuat path_add_beforedan path_add_aftermengizinkan Anda memasukkan jalur sebelum / setelah jalur tertentu yang sudah ada di PATH.

Set fungsi lengkap tersedia di path_helpers.sh di dotfiles saya . Mereka sepenuhnya mendukung penghapusan / penambahan / prepending / penyisipan di awal / tengah / akhir dari string PATH.

jimeh
sumber