Cách tốt nhất để xóa đường dẫn khỏi biến $ PATH trong Bash là gì?


114

Hay nói chung hơn, làm cách nào để xóa một mục khỏi danh sách được phân tách bằng dấu hai chấm trong biến môi trường Bash?

Tôi nghĩ rằng tôi đã thấy một cách đơn giản để làm điều này nhiều năm trước, sử dụng các dạng mở rộng biến Bash nâng cao hơn, nhưng nếu vậy tôi đã mất dấu. Một tìm kiếm nhanh trên Google đã cho ra một vài kết quả có liên quan đáng ngạc nhiên và không có kết quả nào mà tôi gọi là "đơn giản" hay "thanh lịch". Ví dụ: hai phương pháp sử dụng sed và awk, tương ứng:

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)

Không có gì đơn giản tồn tại? Có điều gì tương tự với hàm split () trong Bash không?

Cập nhật:
Có vẻ như tôi cần phải xin lỗi vì câu hỏi mơ hồ có chủ ý của mình; Tôi ít quan tâm đến việc giải quyết một trường hợp sử dụng cụ thể hơn là khuyến khích thảo luận tốt. May mắn thay, tôi đã hiểu nó!

Có một số kỹ thuật rất thông minh ở đây. Cuối cùng, tôi đã thêm ba chức năng sau vào hộp công cụ của mình. Điều kỳ diệu xảy ra trong path_remove, phần lớn dựa trên cách sử dụng thông minh awkbiến RS của 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/:$//'`; }

Điểm mấu chốt thực sự duy nhất trong đó là việc sử dụng sedđể loại bỏ dấu hai chấm ở cuối. Tuy nhiên, xét đến mức độ đơn giản của giải pháp còn lại của Martin, tôi khá sẵn lòng sống với nó!


Câu hỏi liên quan: Làm cách nào để thao tác các phần tử $ PATH trong các tập lệnh shell?


Đối với bất kỳ biến: WORK=`echo -n ${1} | awk -v RS=: -v ORS=: '$0 != "'${3}'"' | sed 's/:$//'`; eval "export ${2}=${WORK}"nhưng bạn phải gọi nó như func $VAR VAR pattern(dựa trên @ martin-york và @ andrew-Aylett)
vesperto

Câu trả lời:


51

Một phút với awk:

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

Chỉnh sửa: Nó phản hồi các bình luận bên dưới:

$ 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!

Chỉnh sửa để đáp ứng sự cố bảo mật: (không liên quan đến câu hỏi)

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

Thao tác này sẽ xóa bất kỳ dấu hai chấm nào còn lại bằng cách xóa các mục nhập cuối cùng, điều này sẽ thêm .vào đường dẫn của bạn một cách hiệu quả .


1
Không thành công khi cố gắng xóa phần tử cuối cùng hoặc nhiều phần tử: trong trường hợp đầu tiên, nó thêm dir hiện tại (dưới dạng chuỗi trống; một lỗ hổng bảo mật tiềm ẩn), trong trường hợp thứ hai, nó thêm `` dưới dạng phần tử đường dẫn.
Fred Foo

1
@larsmans: Hoạt động tốt đối với tôi. Lưu ý: Thư mục trống không giống với thư mục hiện tại là "./"
Martin York

2
Một chuỗi rỗng như một "thành viên" của PATHbiến không , như một quy luật đặc biệt, biểu thị thư mục hiện tại trong tất cả các vỏ Unix ít nhất là từ V7 Unix của năm 1979. Nó vẫn làm trong bash. Kiểm tra hướng dẫn sử dụng hoặc thử cho chính mình.
Fred Foo

1
@Martin: POSIX không yêu cầu hành vi này nhưng thực hiện tài liệu và cho phép nó: pubs.opengroup.org/onlinepubs/9699919799/basedefs/…
Fred Foo

1
Có một vấn đề thậm chí còn phức tạp hơn khi loại bỏ phần tử cuối cùng với điều này: awk dường như thêm một byte null vào cuối chuỗi . Có nghĩa là nếu bạn nối một thư mục khác vào PATH sau đó, nó sẽ thực tế không được tìm kiếm.
sschuberth

55

Hack bẩn thỉu của tôi:

echo ${PATH} > t1
vi t1
export PATH=$(cat t1)

18
Nó không bao giờ là một dấu hiệu tốt khi các giải pháp rõ ràng nhất là de -automate quá trình này. :-D
Ben Blank

VẬY an toàn hơn nhiều so với các lựa chọn thay thế "thanh lịch".
Jortstek

44

Vì vấn đề lớn với việc thay thế là các trường hợp cuối, vậy làm thế nào để làm cho các trường hợp cuối không khác với các trường hợp khác? Nếu đường dẫn đã có dấu hai chấm ở đầu và cuối, chúng ta có thể chỉ cần tìm kiếm chuỗi mong muốn được bọc bằng dấu hai chấm. Như vậy, chúng ta có thể dễ dàng thêm các dấu hai chấm đó và xóa chúng sau đó.

# 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

Bụt tinh khiết :).


2
Tôi muốn thêm phần hướng dẫn này để có thêm một số lớp phủ mờ: tldp.org/LDP/abs/html/string-manipulation.html
Cyber ​​Oliveira.

1
Tôi đã sử dụng điều này vì nó trông giống như giải pháp đơn giản nhất. Nó rất nhanh và dễ dàng, và bạn có thể dễ dàng kiểm tra công việc của mình với echo $ WORK ngay trước dòng cuối cùng nơi bạn thực sự thay đổi biến PATH.
Phil Gran

2
Hoàn toàn là một viên ngọc nhỏ. Chính xác những gì tôi đã cố gắng làm khi tôi tìm thấy bài đăng này. -Cảm ơn Andrew! BTW: Có thể bạn muốn thêm dấu ngoặc kép xung quanh ": $ PATH:", đề phòng trường hợp nó phải chứa khoảng trắng (giống về "/ usr / bin") và dòng cuối cùng "$ WORK".

2
Cảm ơn, @ PacMan-- :). Tôi khá chắc chắn (vừa thử) bạn không cần khoảng trắng cho các phép gán WORKPATHvì việc mở rộng biến xảy ra sau khi dòng được phân tích cú pháp thành các phần để gán biến và thực thi lệnh. REMOVEcó thể cần phải được trích dẫn, hoặc bạn có thể đặt thẳng chuỗi của mình vào phần thay thế nếu đó là một hằng số.
Andrew Aylett

Tôi luôn bối rối về việc khi nào các chuỗi được bảo toàn, vì vậy tôi bắt đầu luôn trích dẫn kép các chuỗi. Cảm ơn một lần nữa vì đã làm rõ điều này. :)

26

Đây là giải pháp đơn giản nhất mà tôi có thể nghĩ ra:

#!/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[*]}"

Ví dụ trên sẽ loại bỏ bất kỳ phần tử nào trong $ PATH có chứa "usr". Bạn có thể thay thế "* usr *" bằng "/ home / user / bin" để chỉ xóa phần tử đó.

cập nhật mỗi sschuberth

Mặc dù tôi nghĩ rằng không gian trong một $PATHlà một kinh khủng ý tưởng, đây là một giải pháp mà xử lý nó:

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[*]}");

hoặc là

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

2
Là một lớp lót: PATH = $ (IFS = ':'; t = ($ PATH); bỏ đặt IFS; t = ($ {t [@] %% * usr *}); IFS = ':'; echo "$ {t [*]} ");
nicerobot

1
Giải pháp này không hoạt động với các đường dẫn trong PATH có chứa khoảng trắng; nó thay thế chúng bằng dấu hai chấm.
sschuberth

11

Dưới đây là một lớp lót, mặc dù các câu trả lời được chấp nhậnđược xếp hạng cao nhất hiện tại , không thêm các ký tự ẩn vào PATH và có thể đối phó với các đường dẫn chứa khoảng trắng:

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

Cá nhân tôi cũng thấy điều này dễ đọc / dễ hiểu và nó chỉ liên quan đến các lệnh thông thường thay vì sử dụng awk.


2
... và nếu bạn muốn một cái gì đó mà có thể đối phó ngay cả với dòng mới trong tên tập tin, bạn có thể sử dụng này: export PATH=$(p=$(echo $PATH | tr ":" "\0" | grep -v -z "/cygwin/" | tr "\0" ":"); echo ${p%:}) (mặc dù cho là, bạn có thể muốn tự hỏi mình tại sao bạn cần này, nếu bạn làm :))
ehdr

Điều này sẽ loại bỏ các kết quả phù hợp từng phần, có thể không phải là điều bạn muốn; Tôi sẽ sử dụng grep -v "^/path/to/remove\$"hoặcgrep -v -x "/path/to/remove"
ShadSterling

Giải pháp tốt, nhưng bạn có thực sự nghĩ trlà phổ biến hơn awkkhông? ;)
K.-Michael Aye

1
Chắc chắn rồi. Các môi trường có dung lượng nhẹ, như Git Bash trên Windows, thay vì đi kèm với một công cụ đơn giản như trmột trình thông dịch awk.
sschuberth

1
@ehdr: Bạn cần thay thế echo "..."bằng printf "%s" "..."để nó hoạt động trên các đường dẫn giống -evà tương tự. Xem stackoverflow.com/a/49418406/102441
Eric

8

Đây là một giải pháp:

  • là Bash thuần túy,
  • không gọi các quy trình khác (như 'sed' hoặc 'awk'),
  • không thay đổi IFS,
  • không phân nhánh một sub-shell,
  • xử lý các đường dẫn bằng dấu cách và
  • loại bỏ tất cả các lần xuất hiện của đối số trong PATH.

    removeFromPath () {
       pd địa phương
       p = ": $ 1:"
       d = ": $ PATH:"
       d = $ {d // $ p /:}
       d = $ {d / #: /}
       PATH = $ {d /%: /}
    }

4
Tôi thích giải pháp này. Có thể làm cho các tên biến mô tả nhiều hơn?
Anukool

rất đẹp. Tuy nhiên, không hoạt động đối với các đoạn đường dẫn có chứa dấu hoa thị (*). Đôi khi họ đến đó một cách vô tình.
Jörg

6

function __path_remove () {
local D = ": $ {PATH}:";
["$ {D /: $ 1: /:}"! = "$ D"] && PATH = "$ {D /: $ 1: /:}";
PATH = "$ {PATH / #: /}";
xuất PATH = "$ {PATH /%: /}";
}

Dug nó ra khỏi tệp .bashrc của tôi. Khi bạn chơi xung quanh với PATH và nó bị mất, awk / sed / grep sẽ không khả dụng :-)


1
Đó là một điểm rất tốt. (Tôi chưa bao giờ thích thực hiện các tiện ích bên ngoài cho những thứ đơn giản như thế này).

6

Tùy chọn bash thuần túy tốt nhất mà tôi đã tìm thấy cho đến nay là:

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
}

Điều này dựa trên câu trả lời không hoàn toàn chính xác cho Thêm thư mục vào $ PATH nếu nó chưa có trên Superuser.


Điều này cũng khá tốt. Tôi đã thử nghiệm nó. Nếu có một đường dẫn trùng lặp (ví dụ: hai đường dẫn hoàn toàn giống nhau) trong PATH, thì chỉ một trong số chúng bị xóa. Bạn cũng có thể làm cho nó thành một lớp lót:removePath () { PATH=${PATH/":$1"/}; PATH=${PATH/"$1:"/}; }

Giải pháp này không thành công khi $PATHchứa một thư mục con của đường dẫn đích (tức là bị xóa). Ví dụ: a:abc/def/bin:b-> a/bin:b, khi nào abc/defthì được xóa.
Robin Hsu

5

Tôi vừa mới sử dụng các hàm trong bản phân phối bash, dường như đã ở đó từ năm 1991. Các hàm này vẫn nằm trong gói bash-docs trên Fedora và đã từng được sử dụng trong /etc/profile, nhưng không còn nữa ...

$ 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" <sjg@zen.void.oz.au>
#Message-Id: <199510091130.VAA01188@zen.void.oz.au>
#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 <sjg@zen.void.oz.au>

#       @(#)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;:\$;;"`
}

4

Tôi đã viết một câu trả lời cho điều này ở đây (sử dụng awk quá). Nhưng tôi không chắc đó là những gì bạn đang tìm kiếm? Ít nhất nó có vẻ rõ ràng đối với tôi những gì nó làm, thay vì cố gắng lắp vào một dòng. Tuy nhiên, đối với một lớp lót đơn giản, chỉ loại bỏ những thứ, tôi khuyên bạn nên

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

Thay thế là

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

hoặc (ngắn hơn nhưng ít đọc hơn)

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

Dù sao, đối với câu hỏi tương tự và rất nhiều câu trả lời hữu ích, hãy xem tại đây .


Và nếu bạn muốn loại bỏ một dòng có sử dụng một phần chuỗi awk '$0 !~ "/bin"'. Tức là giữ các dòng không chứa '/ bin' với toán tử awk !~.
thoni56

3

Vâng, trong bash, vì nó hỗ trợ biểu thức chính quy, tôi chỉ cần làm:

PATH=${PATH/:\/home\/user\/bin/}

Nó không phải chỉ mở rộng tên đường dẫn, không phải biểu thức chính quy?
dreamlax

2
Mặc dù bash hỗ trợ các biểu thức chính quy (kể từ bash 3), đây không phải là một ví dụ về nó, đây là một thay thế biến.
Robert Gamble

4
Đây là mẫu biến thiên và giải pháp có một số vấn đề. 1) nó sẽ không khớp với phần tử đầu tiên. 2) nó sẽ khớp với bất kỳ thứ gì bắt đầu bằng "/ home / user / bin", không chỉ "/ home / user / bin". 3) nó yêu cầu thoát các ký tự đặc biệt. Tốt nhất, tôi muốn nói rằng đây là một ví dụ chưa hoàn chỉnh.
nicerobot

2

Tôi thích ba chức năng được hiển thị trong bản cập nhật của @ BenBlank cho câu hỏi ban đầu của anh ấy. Để tổng quát hóa chúng, tôi sử dụng biểu mẫu 2 đối số, cho phép tôi đặt PATH hoặc bất kỳ biến môi trường nào khác mà tôi muốn:

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/:$//'`"; }

Ví dụ về việc sử dụng:

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

Lưu ý rằng tôi cũng đã thêm một số dấu ngoặc kép để cho phép xử lý thích hợp các tên đường dẫn có chứa khoảng trắng.


2

Cách tốt nhất để xóa đường dẫn khỏi biến $ PATH trong Bash là gì?

Còn gì tao nhã hơn awk?

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

Con trăn! Đó là một giải pháp dễ đọc hơn và dễ bảo trì hơn, và dễ dàng kiểm tra để biết rằng nó thực sự đang làm những gì bạn muốn.

Giả sử bạn muốn xóa phần tử đường dẫn đầu tiên?

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

(Thay vì đường ống từ echo, os.getenv['PATH']sẽ ngắn hơn một chút và cung cấp kết quả tương tự như ở trên, nhưng tôi lo lắng rằng Python có thể làm điều gì đó với biến môi trường đó, vì vậy có lẽ tốt nhất bạn nên chuyển trực tiếp từ môi trường bạn quan tâm .)

Tương tự để loại bỏ từ cuối:

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

Ví dụ: để tạo các hàm shell có thể tái sử dụng này mà bạn có thể đưa vào tệp .bashrc của mình:

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))")"
}

1

Có, ví dụ, đặt dấu hai chấm ở cuối PATH sẽ làm cho việc xóa đường dẫn bớt vụng về và dễ xảy ra lỗi hơn một chút.

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 

1

Nếu bạn lo lắng về việc loại bỏ các bản sao trong $ PATH, cách tốt nhất, IMHO, sẽ không thêm chúng ngay từ đầu. Trong 1 dòng:

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

Thư mục $ có thể được thay thế bằng bất kỳ thứ gì và có thể chứa khoảng trắng ("/ home / user / my document")


1

Giải pháp bash tinh khiết thanh lịch nhất mà tôi tìm thấy cho đến nay:

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"                                  
} 

1

Hầu hết các giải pháp được đề xuất khác chỉ dựa vào đối sánh chuỗi và không tính đến các phân đoạn đường dẫn chứa các tên đặc biệt như . , ..hoặc ~. Hàm bash bên dưới giải quyết các chuỗi thư mục trong đối số của nó và trong các phân đoạn đường dẫn để tìm các đối sánh thư mục logic cũng như các đối sánh chuỗi.

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}
}

Kiểm tra:

$ 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'

cộng một cho bên ngoài hộp
Martin York

1

Linux từ Scratch định nghĩa ba hàm Bash trong /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

Tham khảo: http://www.linuxfromscratch.org/blfs/view/svn/postlfs/profile.html


1

Tôi biết câu hỏi này hỏi về BASH, mà mọi người nên thích hơn, nhưng vì tôi thích sự đối xứng và đôi khi tôi được yêu cầu sử dụng "csh", tôi đã xây dựng tương đương với "path_prepend ()", "path_append ()" và "path_remove () “giải pháp tao nhã ở trên.

Ý chính là "csh" không có các chức năng, vì vậy tôi đặt các tập lệnh shell nhỏ vào thư mục bin cá nhân của mình để hoạt động giống như các chức năng. Tôi tạo bí danh để NGUỒN các tập lệnh đó để thực hiện các thay đổi biến môi trường được chỉ định.

~ / 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'"

Bạn có thể sử dụng chúng như thế này ...

%(csh)> path_append MODULEPATH ${HOME}/modulefiles

0

Vì điều này có xu hướng khá rắc rối, vì KHÔNG có cách nào thanh lịch, tôi khuyên bạn nên tránh vấn đề bằng cách sắp xếp lại giải pháp: xây dựng PATH của bạn thay vì cố gắng phá bỏ nó.

Tôi có thể nói rõ hơn nếu tôi biết bối cảnh vấn đề thực sự của bạn. Tạm thời, tôi sẽ sử dụng một bản dựng phần mềm làm bối cảnh.

Một vấn đề phổ biến với các bản dựng phần mềm là nó bị hỏng trên một số máy, cuối cùng là do cách ai đó đã định cấu hình trình bao mặc định của họ (PATH và các biến môi trường khác). Giải pháp thanh lịch là làm cho các tập lệnh xây dựng của bạn trở nên miễn dịch bằng cách chỉ định đầy đủ môi trường shell. Viết mã các tập lệnh xây dựng của bạn để đặt PATH và các biến môi trường khác dựa trên việc lắp ráp các phần mà bạn kiểm soát, chẳng hạn như vị trí của trình biên dịch, thư viện, công cụ, thành phần, v.v. Làm cho mỗi mục có thể định cấu hình trở thành thứ mà bạn có thể đặt, xác minh và sau đó sử dụng một cách thích hợp trong tập lệnh của bạn.

Ví dụ: tôi có một bản dựng Java nhắm mục tiêu dựa trên WebLogic dựa trên Maven mà tôi được thừa kế ở công ty mới của mình. Kịch bản xây dựng nổi tiếng là mỏng manh, và một nhân viên mới khác và tôi đã dành ba tuần (không phải toàn thời gian, chỉ ở đây đó, nhưng vẫn nhiều giờ) để nó hoạt động trên máy của chúng tôi. Một bước thiết yếu là tôi đã kiểm soát PATH để tôi biết chính xác Java nào, Maven nào và WebLogic nào đang được gọi. Tôi đã tạo các biến môi trường để trỏ đến từng công cụ đó, sau đó tôi tính toán PATH dựa trên các biến đó cộng với một số công cụ khác. Các kỹ thuật tương tự đã thuần hóa các cài đặt có thể định cấu hình khác, cho đến khi cuối cùng chúng tôi tạo ra một bản dựng có thể tái tạo.

Nhân tiện, không sử dụng Maven, Java không sao, và chỉ mua WebLogic nếu bạn thực sự cần phân cụm của nó (nhưng nếu không thì không, và đặc biệt không phải là các tính năng độc quyền của nó).

Lời chúc tốt nhất.


đôi khi bạn không có quyền truy cập root và quản trị viên của bạn quản lý của bạn PATH. Chắc chắn, bạn có thể xây dựng cho riêng mình, nhưng mỗi khi quản trị viên của bạn di chuyển một cái gì đó, bạn phải tìm ra nơi anh ta đặt nó. Kiểu đó đánh bại mục đích của việc có quản trị viên.
Shep

0

Như với @litb, tôi đã đóng góp một câu trả lời cho câu hỏi " Làm cách nào để thao tác các phần tử $ PATH trong các tập lệnh shell ", vì vậy câu trả lời chính của tôi là ở đó.

Chức năng 'phân tách' trong bashvà các dẫn xuất khác của vỏ Bourne đạt được một cách gọn gàng nhất với $IFS, bộ tách liên trường. Ví dụ, để thiết lập các tham số vị trí ( $1, $2, ...) đến các yếu tố của PATH, sử dụng:

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

Nó sẽ hoạt động OK miễn là không có khoảng trắng trong $ PATH. Làm cho nó hoạt động cho các phần tử đường dẫn chứa khoảng trắng là một bài tập không hề nhỏ - dành cho người đọc quan tâm. Có lẽ đơn giản hơn để giải quyết nó bằng cách sử dụng một ngôn ngữ kịch bản như Perl.

Tôi cũng có một kịch bản, clnpath mà tôi sử dụng rộng rãi để thiết lập PATH của mình. Tôi đã ghi lại nó trong câu trả lời cho " Cách tránh sao chép biến PATH trong csh ".


IFS =: a = ($ PATH); IFS = chia tách cũng tốt. hoạt động nếu chúng cũng chứa khoảng trắng. nhưng sau đó bạn có một mảng, và phải tìm kiếm các vòng lặp for và như vậy để xóa tên.
Johannes Schaub - litb

Đúng; nó trở nên khó hiểu - như với nhận xét cập nhật của tôi, có lẽ đơn giản hơn là sử dụng một ngôn ngữ script vào thời điểm này.
Jonathan Leffler

0

Điều làm cho vấn đề này khó chịu là các trường hợp cột hàng rào giữa các yếu tố đầu tiên và cuối cùng. Vấn đề có thể được giải quyết một cách dễ hiểu bằng cách thay đổi IFS và sử dụng một mảng, nhưng tôi không biết làm thế nào để giới thiệu lại dấu hai chấm khi đường dẫn được chuyển đổi sang dạng mảng.

Đây là một phiên bản kém thanh lịch hơn một chút, loại bỏ một thư mục chỉ $PATHsử dụng thao tác chuỗi. Tôi đã thử nghiệm nó.

#!/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"

0

Đây là một lớp lót Perl:

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

Các $abiến được con đường phải được loại bỏ. Các s(thay thế) và printlệnh ngầm hoạt động trên $_biến.


0

Những thứ tốt ở đây. Tôi sử dụng cái này để tránh thêm các bản lừa đảo ngay từ đầu.

#!/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

1
Bạn có thể đơn giản hóa tuyên bố trường hợp của bạn bằng cách thêm một dấu hai chấm ở đầu và đuôi vào chuỗi PATH:case ":$PATH:" in (*:"$nodup":*) ;; (*) PATH="$PATH:$nodup" ;; esac
glenn Jackman

0

Khi bật chế độ nhấp nháy mở rộng, bạn có thể thực hiện những việc sau:

# 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

0

Một lớp lót hình cầu mở rộng (đại loại là):

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

Có vẻ như không cần phải thoát khỏi mức giảm giá $ 1.

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

0

Thêm dấu hai chấm vào PATH, chúng ta cũng có thể thực hiện một số việc như:

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 

0

Trong path_remove_all (bởi proxxy):

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

0

Mặc dù đây là một chủ đề rất cũ, tôi nghĩ rằng giải pháp này có thể được quan tâm:

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

tìm thấy nó trên bài đăng blog này . Tôi nghĩ tôi thích cái này nhất :)


0

Tôi đã thực hiện một cách tiếp cận hơi khác so với hầu hết mọi người ở đây và tập trung đặc biệt vào chỉ thao tác chuỗi, như vậy:

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#?}
}

Trên đây là một ví dụ đơn giản về các hàm cuối cùng mà tôi sử dụng. Tôi cũng đã tạo path_add_beforepath_add_aftercho phép bạn chèn một đường dẫn trước / sau một đường dẫn cụ thể đã có trong PATH.

Tập hợp đầy đủ các chức năng có sẵn trong path_helpers.sh trong dotfiles của tôi . Chúng hoàn toàn hỗ trợ loại bỏ / thêm / thêm / chèn vào đầu / giữa / cuối chuỗi PATH.

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.