วิธีใดเป็นวิธีที่ดีที่สุดในการลบเส้นทางออกจากตัวแปร $ PATH ใน Bash


114

หรือโดยทั่วไปฉันจะลบรายการออกจากรายการที่คั่นด้วยโคลอนในตัวแปรสภาพแวดล้อม Bash ได้อย่างไร

ฉันคิดว่าฉันเคยเห็นวิธีง่ายๆในการทำเมื่อหลายปีก่อนโดยใช้รูปแบบการขยายตัวแปร Bash ขั้นสูง แต่ถ้าเป็นเช่นนั้นฉันก็หลงทาง การค้นหา Google อย่างรวดเร็วพบผลลัพธ์ที่เกี่ยวข้องเพียงไม่กี่รายการและไม่มีสิ่งใดที่ฉันจะเรียกว่า "เรียบง่าย" หรือ "หรูหรา" ตัวอย่างเช่นสองวิธีโดยใช้ sed และ awk ตามลำดับ:

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)

ไม่มีอะไรตรงไปตรงมา? มีอะไรที่คล้ายคลึงกับฟังก์ชัน split () ใน Bash หรือไม่?

ปรับปรุง:
ดูเหมือนว่าฉันต้องขอโทษสำหรับคำถามที่คลุมเครือโดยเจตนา ฉันไม่ค่อยสนใจที่จะแก้ปัญหาการใช้งานที่เฉพาะเจาะจงมากกว่าการกระตุ้นให้เกิดการสนทนาที่ดี โชคดีที่ได้มา!

มีเทคนิคที่ชาญฉลาดอยู่ที่นี่ ในท้ายที่สุดฉันได้เพิ่มฟังก์ชั่นสามอย่างต่อไปนี้ในกล่องเครื่องมือของฉัน เวทมนตร์เกิดขึ้นใน path_remove ซึ่งส่วนใหญ่มาจากการใช้awkตัวแปร RS อย่างชาญฉลาดของ 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/:$//'`; }

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


คำถามที่เกี่ยวข้อง: ฉันจะจัดการองค์ประกอบ $ PATH ในเชลล์สคริปต์ได้อย่างไร


สำหรับตัวแปรใด ๆ : WORK=`echo -n ${1} | awk -v RS=: -v ORS=: '$0 != "'${3}'"' | sed 's/:$//'`; eval "export ${2}=${WORK}"แต่คุณต้องเรียกมันว่าfunc $VAR VAR pattern(อิงตาม @ martin-york และ @ andrew-aylett)
vesperto

คำตอบ:


51

นาทีกับ awk:

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

แก้ไข: ตอบสนองต่อความคิดเห็นด้านล่าง:

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

แก้ไขเพื่อตอบสนองปัญหาด้านความปลอดภัย: (ที่ไม่เกี่ยวข้องกับคำถาม)

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

การดำเนินการนี้จะลบเครื่องหมายทวิภาคต่อท้ายที่เหลือโดยการลบรายการสุดท้ายซึ่งจะเพิ่ม.ไปยังเส้นทางของคุณได้อย่างมีประสิทธิภาพ


1
ล้มเหลวเมื่อพยายามลบองค์ประกอบสุดท้ายหรือหลายองค์ประกอบ: ในกรณีแรกจะเพิ่ม dir ปัจจุบัน (เป็นสตริงว่างช่องความปลอดภัยที่อาจเกิดขึ้น) ในกรณีที่สองจะเพิ่ม `เป็นองค์ประกอบเส้นทาง
Fred Foo

1
@larsmans: ใช้ได้ดีสำหรับฉัน หมายเหตุ: Empty ไม่เหมือนกับไดเร็กทอรีปัจจุบันซึ่งเป็น "./"
Martin York

2
สตริงว่างในฐานะ "สมาชิก" ของPATHตัวแปรจะแสดงถึงไดเร็กทอรีปัจจุบันในเชลล์ Unix ทั้งหมดตั้งแต่อย่างน้อย V7 Unix ของปี 1979 มันยังคงอยู่ในbash. ตรวจสอบคู่มือหรือลองด้วยตัวคุณเอง
Fred Foo

1
@Martin: POSIX ไม่ต้องการพฤติกรรมนี้ แต่ทำเอกสารและอนุญาต: pubs.opengroup.org/onlinepubs/9699919799/basedefs/…
Fred Foo

1
มีแม้กระทั่งปัญหาที่ลึกซึ้งยิ่งขึ้นเมื่อเอาองค์ประกอบที่ผ่านมาด้วยนี้awk ดูเหมือนว่าจะเพิ่มไบต์โมฆะไปยังจุดสิ้นสุดของสตริง หมายความว่าหากคุณต่อท้ายไดเร็กทอรีอื่นเข้ากับ PATH ในภายหลังจะไม่ถูกค้นหาในความเป็นจริง
sschuberth

55

แฮ็คสกปรกของฉัน:

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

18
มันไม่เคยเป็นสัญญาณที่ดีเมื่อวิธีแก้ปัญหาที่ชัดเจนที่สุดคือการยกเลิกกระบวนการอัตโนมัติ :-D
Ben Blank

ปลอดภัยกว่าทางเลือกที่ "หรูหรา" มาก
Jortstek

44

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

# 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

ทุบตีบริสุทธิ์ :)


2
ฉันจะเพิ่มส่วนบทช่วยสอนนี้สำหรับฟรอสติ้ง
Cyber ​​Oliveira

1
ฉันใช้สิ่งนี้เพราะดูเหมือนวิธีแก้ปัญหาที่ง่ายที่สุด มันเร็วและง่ายมากและคุณสามารถตรวจสอบงานของคุณได้อย่างง่ายดายด้วย echo $ WORK ก่อนบรรทัดสุดท้ายที่คุณเปลี่ยนตัวแปร PATH จริงๆ
Phil Gran

2
อัญมณีเล็ก ๆ น้อย ๆ อย่างแน่นอน สิ่งที่พยายามทำเมื่อพบโพสต์นี้ - ขอบคุณแอนดรูว์! BTW: บางทีคุณอาจต้องการเพิ่มเครื่องหมายคำพูดคู่รอบ ๆ ": $ PATH:" ในกรณีที่ควรมีช่องว่าง (เหมือนกันเกี่ยวกับ "/ usr / bin") และบรรทัดสุดท้าย "$ WORK"

2
ขอบคุณ @ PacMan-- :) ฉันค่อนข้างแน่ใจ (เพิ่งลองใช้) คุณไม่ต้องการช่องว่างสำหรับการกำหนดWORKและPATHเนื่องจากการขยายตัวแปรเกิดขึ้นหลังจากที่บรรทัดถูกแยกวิเคราะห์เป็นส่วนสำหรับการกำหนดตัวแปรและการดำเนินการคำสั่ง REMOVEอาจต้องยกมาหรือคุณอาจใส่สตริงตรงเข้าไปแทนที่ถ้าค่าคงที่
Andrew Aylett

ฉันมักจะสับสนอยู่เสมอว่าเมื่อใดที่สตริงถูกเก็บรักษาไว้ดังนั้นฉันจึงเริ่มใช้สตริงแบบ double-quote เสมอ ขอขอบคุณอีกครั้งที่ชี้แจงเรื่องนี้ :)

26

นี่คือทางออกที่ง่ายที่สุดที่ฉันคิดได้:

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

ตัวอย่างข้างต้นจะลบองค์ประกอบใด ๆ ใน $ PATH ที่มี "usr" คุณสามารถแทนที่ "* usr *" ด้วย "/ home / user / bin" เพื่อลบเฉพาะองค์ประกอบนั้น

อัปเดตต่อsschuberth

แม้ว่าฉันจะคิดว่าช่องว่างใน a $PATHเป็นความคิดที่น่ากลัวแต่นี่คือทางออกที่จัดการกับมัน:

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

หรือ

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
เป็นซับเดียว: PATH = $ (IFS = ':'; t = ($ PATH); unset IFS; t = ($ {t [@] %% * usr *}); IFS = ':'; echo "$ {t [*]} ");
nicerobot

1
โซลูชันนี้ใช้ไม่ได้กับเส้นทางใน PATH ที่มีช่องว่าง มันจะแทนที่ด้วยเครื่องหมายทวิภาค
sschuberth

11

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

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

โดยส่วนตัวแล้วฉันยังพบว่าอ่าน / เข้าใจง่ายและเกี่ยวข้องกับคำสั่งทั่วไปเท่านั้นแทนที่จะใช้ awk


2
... และหากคุณต้องการบางสิ่งที่สามารถรับมือได้แม้กระทั่งการขึ้นบรรทัดใหม่ในชื่อไฟล์คุณสามารถใช้สิ่งนี้: export PATH=$(p=$(echo $PATH | tr ":" "\0" | grep -v -z "/cygwin/" | tr "\0" ":"); echo ${p%:}) (แม้ว่าเนื้อหาคุณอาจต้องการถามตัวเองว่าทำไมคุณถึงต้องการสิ่งนี้ถ้าคุณทำ :))
ehdr

การดำเนินการนี้จะลบการจับคู่บางส่วนซึ่งอาจไม่ใช่สิ่งที่คุณต้องการ ฉันจะใช้grep -v "^/path/to/remove\$"หรือgrep -v -x "/path/to/remove"
ShadSterling

วิธีแก้ปัญหาที่ดี แต่คุณคิดว่าtrเป็นเรื่องธรรมดามากกว่าawkหรือไม่? ;)
K. -Michael Aye

1
อย่างแน่นอน สภาพแวดล้อมที่มีน้ำหนักเบาเช่น Git Bash บน Windows ค่อนข้างมาด้วยเครื่องมือง่ายๆเช่นแทนที่จะล่ามเช่นtr awk
sschuberth

1
@ehdr: คุณต้องแทนที่echo "..."ด้วยprintf "%s" "..."เพื่อให้มันทำงานบนเส้นทางที่เหมือน-eและคล้ายกัน ดูstackoverflow.com/a/49418406/102441
Eric

8

นี่คือวิธีแก้ปัญหาที่:

  • เป็น Bash ที่บริสุทธิ์
  • ไม่เรียกใช้กระบวนการอื่น ๆ (เช่น 'sed' หรือ 'awk')
  • ไม่เปลี่ยนแปลง IFS ,
  • ไม่แยกเปลือกย่อย
  • จัดการเส้นทางที่มีช่องว่างและ
  • ลบเหตุการณ์ที่เกิดขึ้นทั้งหมดใน PATHเอาเหตุการณ์ทั้งหมดของการโต้แย้งใน

    removeFromPath () {
       pd ในท้องถิ่น
       p = ": $ 1:"
       d = ": $ PATH:"
       d = $ {d // $ / p:}
       d = $ {d / #: /}
       PATH = $ {d /% /}
    }

4
ฉันชอบวิธีนี้ อาจทำให้ชื่อตัวแปรมีความหมายมากขึ้น?
Anukool

ดีมาก. อย่างไรก็ตามไม่สามารถใช้ได้กับส่วนของเส้นทางที่มีเครื่องหมายดอกจัน (*) บางครั้งพวกเขาไปที่นั่นโดยบังเอิญ
Jörg

6

ฟังก์ชัน __path_remove () {
local D = ": $ {PATH}:";
["$ {D /: $ 1: /:}"! = "$ D"] && PATH = "$ {D /: $ 1: /:}";
PATH = "$ {เส้นทาง / #: /}";
ส่งออก PATH = "$ {PATH /%: /}";
}

ขุดออกจากไฟล์. bashrc ของฉัน เมื่อคุณเล่นกับ PATH และมันหายไป awk / sed / grep จะไม่พร้อมใช้งาน :-)


1
นั่นเป็นจุดที่ดีมาก (ฉันไม่เคยชอบการเรียกใช้ยูทิลิตี้ภายนอกสำหรับสิ่งง่ายๆเช่นนี้)

6

ตัวเลือกทุบตีบริสุทธิ์ที่ดีที่สุดที่ฉันพบมีดังต่อไปนี้:

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
}

นี่เป็นไปตามคำตอบที่ไม่ถูกต้องในการเพิ่มไดเร็กทอรีไปที่ $ PATH หากยังไม่มีใน Superuser


นี่ก็ค่อนข้างดีเช่นกัน ฉันทดสอบแล้ว หากมีเส้นทางที่ซ้ำกัน (เช่นสองเส้นทางที่เหมือนกันทุกประการ) ใน PATH จะมีเพียงหนึ่งในนั้นเท่านั้นที่จะถูกลบออก คุณสามารถทำให้เป็นซับremovePath () { PATH=${PATH/":$1"/}; PATH=${PATH/"$1:"/}; }

วิธีนี้ล้มเหลวเมื่อ$PATHมีพา ธ ย่อยของเป้าหมาย (เช่นจะถูกลบ) ตัวอย่างเช่น: a:abc/def/bin:b-> a/bin:bเมื่อabc/defเป็นที่จะถูกลบ
Robin Hsu

5

ฉันเพิ่งใช้ฟังก์ชั่นในการกระจาย bash ซึ่งเห็นได้ชัดว่ามีมาตั้งแต่ปี 1991 สิ่งเหล่านี้ยังอยู่ในแพ็คเกจ bash-docs บน Fedora และเคยใช้มา/etc/profileแล้ว แต่ไม่มีอีกแล้ว ...

$ 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

ฉันเขียนคำตอบไว้ที่นี่ (ใช้ awk ด้วย) แต่ฉันไม่แน่ใจว่านั่นคือสิ่งที่คุณกำลังมองหา? อย่างน้อยก็ดูชัดเจนสำหรับฉันว่ามันทำอะไรแทนที่จะพยายามทำให้พอดีกับบรรทัดเดียว แต่สำหรับซับในแบบธรรมดาที่เอาเฉพาะบางสิ่งออกไปฉันขอแนะนำ

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

การเปลี่ยนคือ

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

หรือ (สั้นกว่า แต่อ่านได้น้อยกว่า)

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

อย่างไรก็ตามสำหรับคำถามเดียวกันและเป็นจำนวนมากทั้งของคำตอบที่มีประโยชน์ให้ดูที่นี่


awk '$0 !~ "/bin"'และถ้าคุณต้องการที่จะลบเส้นที่มีการใช้งานสตริงบางส่วน คือให้สายที่ไม่ได้มี '/ bin' !~กับผู้ประกอบการ
thoni56

3

ในการทุบตีเนื่องจากมันรองรับการแสดงออกปกติฉันจะทำ:

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

ไม่ใช่แค่การขยายชื่อพา ธ ไม่ใช่นิพจน์ทั่วไปใช่หรือไม่
dreamlax

2
แม้ว่า bash จะรองรับนิพจน์ทั่วไป (ณ bash 3) แต่นี่ไม่ใช่ตัวอย่างของมัน แต่เป็นการทดแทนตัวแปร
Robert Gamble

4
นี่คือการขยายตัวแปรรูปแบบและการแก้ปัญหามีปัญหาหลายประการ 1) จะไม่ตรงกับองค์ประกอบแรก 2) จะจับคู่สิ่งที่ขึ้นต้นด้วย "/ home / user / bin" ไม่ใช่แค่ "/ home / user / bin" 3) ต้องใช้อักขระพิเศษที่หลบหนี อย่างดีที่สุดฉันจะบอกว่านี่เป็นตัวอย่างที่ไม่สมบูรณ์
nicerobot

2

ฉันชอบฟังก์ชั่นสามอย่างที่แสดงในการอัปเดตคำถามเดิมของ @ BenBlank เพื่อสรุปให้ชัดเจนฉันใช้รูปแบบ 2 อาร์กิวเมนต์ซึ่งอนุญาตให้ฉันตั้งค่า PATH หรือตัวแปรสภาพแวดล้อมอื่น ๆ ที่ฉันต้องการ:

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

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

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

โปรดทราบว่าฉันได้เพิ่มเครื่องหมายคำพูดเพื่อให้สามารถประมวลผลชื่อพา ธ ที่มีช่องว่างได้อย่างเหมาะสม


2

วิธีใดเป็นวิธีที่ดีที่สุดในการลบเส้นทางออกจากตัวแปร $ PATH ใน Bash

สวยหรูกว่า awk ยังไง?

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

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

สมมติว่าคุณต้องการลบองค์ประกอบเส้นทางแรกหรือไม่

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

(แทนที่จะท่อจากecho, os.getenv['PATH']จะเป็นเพียงเล็กน้อยสั้นและให้ผลเช่นเดียวกับข้างต้น แต่ฉันกังวลว่างูใหญ่อาจจะทำอะไรกับตัวแปรสภาพแวดล้อมที่ดังนั้นมันอาจจะดีที่สุดในท่อได้โดยตรงจากสภาพแวดล้อมที่คุณห่วงใย .)

ในทำนองเดียวกันเพื่อลบออกจากจุดสิ้นสุด:

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

ในการสร้างฟังก์ชันเชลล์ที่ใช้ซ้ำได้เหล่านี้ซึ่งคุณสามารถทำได้ตัวอย่างเช่นติดในไฟล์. bashrc ของคุณ:

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

ใช่การใส่เครื่องหมายจุดคู่ที่ส่วนท้ายของ PATH ทำให้การลบพา ธ ดูงุ่มง่ามน้อยลงและเกิดข้อผิดพลาดได้ง่าย

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

หากคุณกังวลเกี่ยวกับการลบรายการที่ซ้ำกันใน $ PATH วิธีที่ดีที่สุด IMHO คือไม่ต้องเพิ่มรายการเหล่านี้ตั้งแต่แรก ใน 1 บรรทัด:

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

$ โฟลเดอร์สามารถถูกแทนที่ด้วยอะไรก็ได้และอาจมีช่องว่าง ("/ home / user / my documents")


1

โซลูชันทุบตีบริสุทธิ์ที่สง่างามที่สุดที่ฉันพบจนถึงปัจจุบัน:

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

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

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

ทดสอบ:

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

บวกหนึ่งสำหรับนอกกรอบ
Martin York

1

Linux จาก Scratch กำหนดฟังก์ชัน Bash สามอย่างใน/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

อ้างอิง: http://www.linuxfromscratch.org/blfs/view/svn/postlfs/profile.html


1

ฉันรู้ว่าคำถามนี้ถามเกี่ยวกับ BASH ซึ่งทุกคนควรชอบ แต่เนื่องจากฉันชอบความสมมาตรและบางครั้งฉันจำเป็นต้องใช้ "csh" ฉันจึงสร้างสิ่งที่เทียบเท่ากับ "path_prepend ()", "path_append ()" และ "path_remove () "วิธีแก้ปัญหาที่หรูหราข้างต้น

ความสำคัญคือ "csh" ไม่มีฟังก์ชันดังนั้นฉันจึงใส่เชลล์สคริปต์เล็กน้อยในไดเร็กทอรี bin ส่วนบุคคลของฉันที่ทำหน้าที่เหมือนฟังก์ชัน ฉันสร้างนามแฝงเพื่อ SOURCE สคริปต์เหล่านั้นเพื่อทำการเปลี่ยนแปลงตัวแปรสภาพแวดล้อมที่กำหนด

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

ใช้แบบนี้ก็ได้ ...

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

0

เนื่องจากสิ่งนี้มีแนวโน้มที่จะเป็นปัญหามากเนื่องจากไม่มีวิธีที่หรูหราฉันขอแนะนำให้หลีกเลี่ยงปัญหาโดยการจัดเรียงวิธีแก้ปัญหาใหม่: สร้างเส้นทางของคุณขึ้นแทนที่จะพยายามทำลายมัน

ฉันจะเจาะจงได้มากกว่านี้ถ้าฉันรู้บริบทปัญหาที่แท้จริงของคุณ ในระหว่างนี้ฉันจะใช้การสร้างซอฟต์แวร์เป็นบริบท

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

ตัวอย่างเช่นฉันมีการสร้าง Java ที่กำหนดเป้าหมายด้วย WebLogic ที่ใช้ Maven ซึ่งฉันได้รับมาจากนายจ้างใหม่ของฉัน สคริปต์การสร้างขึ้นชื่อเรื่องความเปราะบางและมีพนักงานใหม่อีกคนและฉันใช้เวลาสามสัปดาห์ (ไม่ใช่เต็มเวลาแค่ที่นี่และที่นั่น แต่ก็ยังใช้เวลาหลายชั่วโมง) เพื่อให้มันทำงานบนเครื่องของเรา ขั้นตอนสำคัญคือฉันควบคุม PATH เพื่อที่ฉันจะได้รู้ว่า Java ตัวไหน Maven และ WebLogic ใดถูกเรียกใช้ ฉันสร้างตัวแปรสภาพแวดล้อมเพื่อชี้ไปที่แต่ละเครื่องมือเหล่านั้นจากนั้นฉันคำนวณ PATH โดยพิจารณาจากเครื่องมือเหล่านั้นบวกกับตัวแปรอื่น ๆ เทคนิคที่คล้ายกันทำให้การตั้งค่าอื่น ๆ ที่กำหนดค่าได้เชื่องจนในที่สุดเราก็สร้างงานสร้างที่ทำซ้ำได้

อย่างไรก็ตามอย่าใช้ Maven Java ก็โอเคและซื้อ WebLogic เฉพาะในกรณีที่คุณต้องการการจัดกลุ่ม (แต่อย่างอื่นไม่และโดยเฉพาะอย่างยิ่งไม่ใช่คุณสมบัติที่เป็นกรรมสิทธิ์ของมัน)

ด้วยความปรารถนาดี.


บางครั้งคุณไม่มีสิทธิ์เข้าถึงรูทและผู้ดูแลระบบของคุณจะจัดการPATHไฟล์. แน่นอนว่าคุณสามารถสร้างเป็นของตัวเองได้ แต่ทุกครั้งที่ผู้ดูแลระบบของคุณย้ายบางสิ่งคุณจะต้องหาที่ที่เขาวางไว้ ประเภทของการเอาชนะจุดประสงค์ของการมีผู้ดูแลระบบ
Shep

0

เช่นเดียวกับ @litb ฉันให้คำตอบสำหรับคำถาม " ฉันจะจัดการองค์ประกอบ $ PATH ในเชลล์สคริปต์ได้อย่างไร " ดังนั้นคำตอบหลักของฉันจึงอยู่ที่นั่น

ฟังก์ชัน 'แยก' ในbashและอนุพันธ์ของเชลล์บอร์นอื่น ๆ ทำได้อย่างเรียบร้อยที่สุดด้วย$IFSตัวคั่นระหว่างฟิลด์ ยกตัวอย่างเช่นการตั้งข้อโต้แย้งตำแหน่ง ( $1, $2, ... ) เพื่อองค์ประกอบของเส้นทางการใช้งาน:

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

มันจะทำงานได้ดีตราบเท่าที่ไม่มีช่องว่างใน $ PATH การทำให้มันใช้งานได้กับองค์ประกอบเส้นทางที่มีช่องว่างเป็นการออกกำลังกายที่ไม่สำคัญ - ทิ้งไว้สำหรับผู้อ่านที่สนใจ มันอาจจะง่ายกว่าที่จะจัดการกับมันโดยใช้ภาษาสคริปต์เช่น Perl

ฉันยังมีสคริปต์clnpathซึ่งฉันใช้อย่างกว้างขวางในการตั้งค่าเส้นทางของฉัน ฉันบันทึกไว้ในคำตอบของ " วิธีป้องกันไม่ให้ซ้ำตัวแปร PATH ใน csh "


IFS =: a = ($ เส้นทาง); IFS = การแยกก็ดีเช่นกัน ใช้งานได้หากมีช่องว่างด้วย แต่แล้วคุณก็มีอาร์เรย์และต้องเล่นกับลูปเพื่อลบชื่อ
Johannes Schaub - litb

ใช่; มันยุ่งเหยิง - เช่นเดียวกับความคิดเห็นที่อัปเดตของฉันมันอาจจะง่ายกว่าที่จะใช้ภาษาสคริปต์ในตอนนี้
Jonathan Leffler

0

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

นี่คือเวอร์ชันที่หรูหราน้อยกว่าเล็กน้อยที่ลบไดเร็กทอรีหนึ่งรายการจากการ$PATHใช้การจัดการสตริงเท่านั้น ฉันได้ทดสอบแล้ว

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

นี่คือ Perl one-liner:

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

$aตัวแปรได้รับเส้นทางที่จะถูกลบออก s(แทน) และprintคำสั่งโดยปริยายทำงานใน$_ตัวแปร


0

ของดีที่นี่. ฉันใช้อันนี้เพื่อไม่ให้เพิ่มสิ่งที่น่าเบื่อในตอนแรก

#!/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
คุณสามารถทำให้คำชี้แจงกรณีของคุณง่ายขึ้นโดยการเพิ่มเครื่องหมายจุดคู่นำหน้าและต่อท้ายในสตริง PATH:case ":$PATH:" in (*:"$nodup":*) ;; (*) PATH="$PATH:$nodup" ;; esac
glenn jackman

0

ด้วยการเปิดใช้งาน globbing แบบขยายคุณสามารถทำสิ่งต่อไปนี้ได้:

# 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

ขยาย globbing หนึ่งซับ (ดีเรียงลำดับ):

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

ดูเหมือนว่าไม่จำเป็นต้องหนีเครื่องหมายทับในราคา $ 1

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

0

การเพิ่มเครื่องหมายทวิภาคใน PATH เราสามารถทำได้เช่น:

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

ใน path_remove_all (โดย proxxy):

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

0

แม้ว่านี่จะเป็นกระทู้เก่ามาก แต่ฉันคิดว่าวิธีนี้อาจเป็นที่สนใจ:

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

พบในโพสต์บล็อกนี้ คิดว่าชอบอันนี้ที่สุด :)


0

ฉันใช้วิธีการที่แตกต่างจากคนส่วนใหญ่ที่นี่เล็กน้อยและมุ่งเน้นไปที่การจัดการสตริงโดยเฉพาะเช่น:

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

ข้างต้นเป็นตัวอย่างที่เรียบง่ายของฟังก์ชันสุดท้ายที่ฉันใช้ ฉันได้สร้างpath_add_beforeและpath_add_afterอนุญาตให้คุณแทรกเส้นทางก่อน / หลังเส้นทางที่ระบุไว้แล้วใน PATH

ชุดเต็มรูปแบบของฟังก์ชั่นที่มีอยู่ในpath_helpers.shในของฉันdotfiles รองรับการลบ / ต่อท้าย / prepending / แทรกที่จุดเริ่มต้น / กลาง / ท้ายสตริง PATH

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