如何操作shell脚本中的$ PATH元素?

是否有从PATH类似的shellvariables中删除元素的惯用方法?

那是我想要的

PATH=/home/joe/bin:/usr/local/bin:/usr/bin:/bin:/path/to/app/bin:. 

删除replace /path/to/app/bin而不用打开其余的variables。 额外的点让我新的元素在任意位置。 目标将被一个明确定义的string识别,并且可能出现在列表中的任何位置。

我知道我已经看到了这一点,可以自己拼凑一些东西,但我正在寻找一个不错的方法。 便携性和标准化加上。

我使用bash,但是也欢迎你喜欢的shell。


这里的上下文是需要在多个版本(一个用于分析,另一个用于处理框架)之间方便地切换的一个大型科学分析软件包,它产生了几十个可执行文件,数据存储在文件系统周围,并使用环境variables帮助find所有这些东西。 我想编写一个脚本来select一个版本,并且需要能够删除与当前活动版本相关的$PATH元素,并用与新版本相关的相同元素replace它们。


这与重新运行login脚本等时防止重复$PATH元素的问题有关。


  • 上一个类似的问题: 如何防止在csh中复制pathvariables
  • 后续类似的问题: 从Bash中的$ PATHvariables中删除path的最优雅的方法是什么?

从dmckee解决建议的解决方案:

  1. 虽然某些版本的Bash可能允许在函数名称中使用连字符,但其他版本(MacOS X)却不能。
  2. 我没有看到在函数结束之前立即使用return。
  3. 我不认为需要所有的分号。
  4. 我不明白为什么你有路径元素模式导出一个值。 把export看作等同于设置(甚至创建)一个全局变量 – 只要有可能就要避免。
  5. 我不确定你所期望的“ replace-path PATH $PATH /usr ”做什么,但它没有做我期望的。

考虑一个PATH值,该值开始包含:

 . /Users/jleffler/bin /usr/local/postgresql/bin /usr/local/mysql/bin /Users/jleffler/perl/v5.10.0/bin /usr/local/bin /usr/bin /bin /sw/bin /usr/sbin /sbin 

我得到的结果(从' replace-path PATH $PATH /usr ')是:

 . /Users/jleffler/bin /local/postgresql/bin /local/mysql/bin /Users/jleffler/perl/v5.10.0/bin /local/bin /bin /bin /sw/bin /sbin /sbin 

由于/ usr不是作为(完整的)路径元素出现,而只是作为路径元素的一部分,所以我希望得到原始路径。

这可以通过修改其中一个sed命令来修复replace-path

 export $path=$(echo -n $list | tr ":" "\n" | sed "s:^$removestr\$:$replacestr:" | tr "\n" ":" | sed "s|::|:|g") 

我用':'代替'|' 分开替代的部分,因为“|” 可能(理论上)出现在路径组件中,而根据PATH的定义,冒号不能。 我观察到第二个sed可以从PATH中间消除当前目录。 也就是说,PATH的一个合理的(虽然是不正当的)价值可能是:

 PATH=/bin::/usr/local/bin 

处理完成后,当前目录将不再位于PATH上。

匹配path-element-by-pattern类似变化适用于path-element-by-pattern

 export $target=$(echo -n $list | tr ":" "\n" | grep -m 1 "^$pat\$") 

我顺便注意到, grep -m 1不是标准的(这是一个GNU扩展,在MacOS X上也是可用的)。 实际上, echo-n选项也是非标准的; 只需简单地删除由于将换行符从换行符转换为冒号而添加的尾随冒号即可。 由于path-element-by-pattern只被使用一次,具有不良的副作用(它会破坏任何预先存在的导出变量,名为$removestr ),因此可以通过其正文来明智地替换它。 这样,为了避免空间问题或不必要的文件名扩展,可以使用更多的引号来引导:

 # path_tools.bash # # A set of tools for manipulating ":" separated lists like the # canonical $PATH variable. # # /bin/sh compatibility can probably be regained by replacing $( ) # style command expansion with ` ` style ############################################################################### # Usage: # # To remove a path: # replace_path PATH $PATH /exact/path/to/remove # replace_path_pattern PATH $PATH <grep pattern for target path> # # To replace a path: # replace_path PATH $PATH /exact/path/to/remove /replacement/path # replace_path_pattern PATH $PATH <target pattern> /replacement/path # ############################################################################### # Remove or replace an element of $1 # # $1 name of the shell variable to set (eg PATH) # $2 a ":" delimited list to work from (eg $PATH) # $3 the precise string to be removed/replaced # $4 the replacement string (use "" for removal) function replace_path () { path=$1 list=$2 remove=$3 replace=$4 # Allowed to be empty or unset export $path=$(echo "$list" | tr ":" "\n" | sed "s:^$remove\$:$replace:" | tr "\n" ":" | sed 's|:$||') } # Remove or replace an element of $1 # # $1 name of the shell variable to set (eg PATH) # $2 a ":" delimited list to work from (eg $PATH) # $3 a grep pattern identifying the element to be removed/replaced # $4 the replacement string (use "" for removal) function replace_path_pattern () { path=$1 list=$2 removepat=$3 replacestr=$4 # Allowed to be empty or unset removestr=$(echo "$list" | tr ":" "\n" | grep -m 1 "^$removepat\$") replace_path "$path" "$list" "$removestr" "$replacestr" } 

我有一个名为echopath的Perl脚本,在调试类似PATH的变量时遇到了一些问题:

 #!/usr/bin/perl -w # # "@(#)$Id: echopath.pl,v 1.7 1998/09/15 03:16:36 jleffler Exp $" # # Print the components of a PATH variable one per line. # If there are no colons in the arguments, assume that they are # the names of environment variables. @ARGV = $ENV{PATH} unless @ARGV; foreach $arg (@ARGV) { $var = $arg; $var = $ENV{$arg} if $arg =~ /^[A-Za-z_][A-Za-z_0-9]*$/; $var = $arg unless $var; @lst = split /:/, $var; foreach $val (@lst) { print "$val\n"; } } 

当我在下面的测试代码上运行修改后的解决方案时:

 echo xpath=$PATH replace_path xpath $xpath /usr echopath $xpath echo xpath=$PATH replace_path_pattern xpath $xpath /usr/bin /work/bin echopath xpath echo xpath=$PATH replace_path_pattern xpath $xpath "/usr/.*/bin" /work/bin echopath xpath 

输出是:

 . /Users/jleffler/bin /usr/local/postgresql/bin /usr/local/mysql/bin /Users/jleffler/perl/v5.10.0/bin /usr/local/bin /usr/bin /bin /sw/bin /usr/sbin /sbin . /Users/jleffler/bin /usr/local/postgresql/bin /usr/local/mysql/bin /Users/jleffler/perl/v5.10.0/bin /usr/local/bin /work/bin /bin /sw/bin /usr/sbin /sbin . /Users/jleffler/bin /work/bin /usr/local/mysql/bin /Users/jleffler/perl/v5.10.0/bin /usr/local/bin /usr/bin /bin /sw/bin /usr/sbin /sbin 

这对我来说看起来是正确的 – 至少,对于我的定义是什么问题。

我注意到, echopath LD_LIBRARY_PATH评估$LD_LIBRARY_PATH 。 如果你的函数能够这样做,那将是很好的,所以用户可以输入:

 replace_path PATH /usr/bin /work/bin 

这可以通过使用:

 list=$(eval echo '$'$path) 

这导致了这个代码的修改:

 # path_tools.bash # # A set of tools for manipulating ":" separated lists like the # canonical $PATH variable. # # /bin/sh compatibility can probably be regained by replacing $( ) # style command expansion with ` ` style ############################################################################### # Usage: # # To remove a path: # replace_path PATH /exact/path/to/remove # replace_path_pattern PATH <grep pattern for target path> # # To replace a path: # replace_path PATH /exact/path/to/remove /replacement/path # replace_path_pattern PATH <target pattern> /replacement/path # ############################################################################### # Remove or replace an element of $1 # # $1 name of the shell variable to set (eg PATH) # $2 the precise string to be removed/replaced # $3 the replacement string (use "" for removal) function replace_path () { path=$1 list=$(eval echo '$'$path) remove=$2 replace=$3 # Allowed to be empty or unset export $path=$(echo "$list" | tr ":" "\n" | sed "s:^$remove\$:$replace:" | tr "\n" ":" | sed 's|:$||') } # Remove or replace an element of $1 # # $1 name of the shell variable to set (eg PATH) # $2 a grep pattern identifying the element to be removed/replaced # $3 the replacement string (use "" for removal) function replace_path_pattern () { path=$1 list=$(eval echo '$'$path) removepat=$2 replacestr=$3 # Allowed to be empty or unset removestr=$(echo "$list" | tr ":" "\n" | grep -m 1 "^$removepat\$") replace_path "$path" "$removestr" "$replacestr" } 

下面的修改测试现在也起作用:

 echo xpath=$PATH replace_path xpath /usr echopath xpath echo xpath=$PATH replace_path_pattern xpath /usr/bin /work/bin echopath xpath echo xpath=$PATH replace_path_pattern xpath "/usr/.*/bin" /work/bin echopath xpath 

它产生和以前一样的输出。

重新发布我的答案什么是从Bash $ PATH变量中删除路径最优雅的方式? :

 #!/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=$(IFS=':';t=($PATH);unset IFS;t=(${t[@]%%*usr*});IFS=':';echo "${t[*]}"); 

在“ 如何防止在csh中复制路径变量 ”的答案中有几个相关的程序。 他们更注重确保没有重复的元素,但我提供的脚本可以用作:

 export PATH=$(clnpath $head_dirs:$PATH:$tail_dirs $remove_dirs) 

假设在$ head_dirs中有一个或多个目录,在$ tail_dirs中有一个或多个目录,在$ remove_dirs中有一个或多个目录,然后使用shell将头部,当前部分和尾部连接成一个海量值,然后删除在$ remove_dirs中列出的目录(如果它们不存在,则不是错误),以及消除路径中任何目录的第二个和随后的出现。

这不涉及将路径组件放置到特定位置(除开始或结束之外,以及仅间接)。 注意,指定要添加新元素的位置,或要替换的元素是混乱的。

要删除一个元素,你可以使用sed:

 #!/bin/bash NEW_PATH=$(echo -n $PATH | tr ":" "\n" | sed "/foo/d" | tr "\n" ":") export PATH=$NEW_PATH 

将从路径中删除包含“foo”的路径。

您也可以使用sed在给定行之前或之后插入新行。

编辑:您可以通过排序和uniq管道删除重复:

 echo -n $PATH | tr ":" "\n" | sort | uniq -c | sed -n "/ 1 / s/.*1 \(.*\)/\1/p" | sed "/foo/d" | tr "\n" ":" 

只是注意到bash本身可以做搜索和替换。 它可以完成所有正常的“一次或全部”,你可以期望的敏感选项。

从手册页:

$ {参数/模式/字符串}

该模式被扩展为产生一个模式,就像路径名扩展一样。 参数被扩展,模式与其值最长的匹配被替换为字符串。 如果Ipattern以/开始,所有模式的匹配都被替换为字符串。 通常只有第一场比赛被取代。 如果模式以#开始,则它必须在参数的扩展值的开头匹配。 如果模式以%开始,则它必须在参数的扩展值的末尾匹配。 如果string为null,则删除pattern的匹配,并且可以省略/ following模式。 如果参数是@或*,则替换操作依次应用于每个位置参数,扩展是结果列表。 如果参数是一个以@或*为下标的数组变量,那么替换操作将依次应用于数组的每个成员,而扩展则是结果列表。

您还可以通过将$ IFS(输入字段分隔符)设置为所需的分隔符来进行字段拆分。

好的,谢谢所有响应者。 我准备了弗洛林的答案的封装版本。 第一遍看起来像这样:

 # path_tools.bash # # A set of tools for manipulating ":" separated lists like the # canonical $PATH variable. # # /bin/sh compatibility can probably be regained by replacing $( ) # style command expansion with ` ` style ############################################################################### # Usage: # # To remove a path: # replace-path PATH $PATH /exact/path/to/remove # replace-path-pattern PATH $PATH <grep pattern for target path> # # To replace a path: # replace-path PATH $PATH /exact/path/to/remove /replacement/path # replace-path-pattern PATH $PATH <target pattern> /replacement/path # ############################################################################### # Finds the _first_ list element matching $2 # # $1 name of a shell variable to be set # $2 name of a variable with a path-like structure # $3 a grep pattern to match the desired element of $1 function path-element-by-pattern (){ target=$1; list=$2; pat=$3; export $target=$(echo -n $list | tr ":" "\n" | grep -m 1 $pat); return } # Removes or replaces an element of $1 # # $1 name of the shell variable to set (ie PATH) # $2 a ":" delimited list to work from (ie $PATH) # $2 the precise string to be removed/replaced # $3 the replacement string (use "" for removal) function replace-path () { path=$1; list=$2; removestr=$3; replacestr=$4; # Allowed to be "" export $path=$(echo -n $list | tr ":" "\n" | sed "s|$removestr|$replacestr|" | tr "\n" ":" | sed "s|::|:|g"); unset removestr return } # Removes or replaces an element of $1 # # $1 name of the shell variable to set (ie PATH) # $2 a ":" delimited list to work from (ie $PATH) # $2 a grep pattern identifying the element to be removed/replaced # $3 the replacement string (use "" for removal) function replace-path-pattern () { path=$1; list=$2; removepat=$3; replacestr=$4; # Allowed to be "" path-element-by-pattern removestr $list $removepat; replace-path $path $list $removestr $replacestr; } 

在所有的功能中仍然需要陷入错误的陷阱,而我应该坚持重复路径解决方案。

你通过做一个. /include/path/path_tools.bash 在工作脚本中调用. /include/path/path_tools.bash并调用replace-path*函数。


我仍然接受新的和/或更好的答案。

这很容易使用awk。

更换

 { for(i=1;i<=NF;i++) if($i == REM) if(REP) print REP; else continue; else print $i; } 

开始使用

 function path_repl { echo $PATH | awk -F: -f rem.awk REM="$1" REP="$2" | paste -sd: } $ echo $PATH /bin:/usr/bin:/home/js/usr/bin $ path_repl /bin /baz /baz:/usr/bin:/home/js/usr/bin $ path_repl /bin /usr/bin:/home/js/usr/bin 

附加

插入到给定的位置。 默认情况下,它在最后附加。

 { if(IDX < 1) IDX = NF + IDX + 1 for(i = 1; i <= NF; i++) { if(IDX == i) print REP print $i } if(IDX == NF + 1) print REP } 

开始使用

 function path_app { echo $PATH | awk -F: -f app.awk REP="$1" IDX="$2" | paste -sd: } $ echo $PATH /bin:/usr/bin:/home/js/usr/bin $ path_app /baz 0 /bin:/usr/bin:/home/js/usr/bin:/baz $ path_app /baz -1 /bin:/usr/bin:/baz:/home/js/usr/bin $ path_app /baz 1 /baz:/bin:/usr/bin:/home/js/usr/bin 

删除重复项

这一个保持第一次出现。

 { for(i = 1; i <= NF; i++) { if(!used[$i]) { print $i used[$i] = 1 } } } 

像这样开始:

 echo $PATH | awk -F: -f rem_dup.awk | paste -sd: 

验证是否存在所有元素

以下内容将显示文件系统中不存在的所有条目的错误消息,并返回非零值。

 echo -n $PATH | xargs -d: stat -c %n 

为了简单地检查所有元素是否是路径并获得返回码,还可以使用test

 echo -n $PATH | xargs -d: -n1 test -d 

假设

 echo $PATH /usr/lib/jvm/java-1.6.0/bin:lib/jvm/java-1.6.0/bin/:/lib/jvm/java-1.6.0/bin/:/usr/lib/qt-3.3/bin:/usr/lib/ccache:/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin:/home/tvnadeesh/bin 

如果你想删除/lib/jvm/java-1.6.0/bin/像下面那样做

 export PATH=$(echo $PATH | sed 's/\/lib\/jvm\/java-1.6.0\/bin\/://g') 

sed将从echo $PATH获取输入,并替换/lib/jvm/java-1.6.0/bin/:with empty

这样你可以删除

  • PATH的顺序不被distrubed
  • 处理好空路径,路径空间等角落案例
  • dir的部分匹配不会导致误报
  • 以适当的方式对待PATH头部和尾部的路径。 不垃圾等。

假设你有/ foo:/ some / path:/ some / path / dir1:/ some / path / dir2:/ bar并且你想替换/ some / path然后正确地替换“/ some / path” some / path / dir1“或”/ some / path / dir2“,就像你所期望的那样。

 function __path_add(){ if [ -d "$1" ] ; then local D=":${PATH}:"; [ "${D/:$1:/:}" == "$D" ] && PATH="$PATH:$1"; PATH="${PATH/#:/}"; export PATH="${PATH/%:/}"; fi } function __path_remove(){ local D=":${PATH}:"; [ "${D/:$1:/:}" != "$D" ] && PATH="${D/:$1:/:}"; PATH="${PATH/#:/}"; export PATH="${PATH/%:/}"; } # Just for the shake of completeness function __path_replace(){ if [ -d "$2" ] ; then local D=":${PATH}:"; if [ "${D/:$1:/:}" != "$D" ] ; then PATH="${D/:$1:/:$2:}"; PATH="${PATH/#:/}"; export PATH="${PATH/%:/}"; fi fi } 

分享到:相关文章最好的方法是从Bash中的$ PATH变量中删除一个路径?

弹出我的头来只改变一部分字符串的第一件事是sed替代。

例如:if echo $ PATH =>“/ usr / pkg / bin:/ usr / bin:/ bin:/ usr / pkg / games:/ usr / pkg / X11R6 / bin”,则将“/ usr / bin”更改为“/ usr / local / bin”可以这样做:

##生成标准输出文件

##使用“=”字符而不是斜杠(“/”),因为这将是混乱的,#替代引用字符应该不太可能在PATH

##路径分隔符“:”在这里被删除并重新添加,#可能需要在最后一个路径之后多一个冒号

echo $ PATH | sed'= / usr / bin:= / usr / local / bin:='

此解决方案替换整个路径元素,因此如果新元素相似,则可能是冗余的。

如果新的PATH's不是动态的,但总是在一些常量集合内,你可以将它们保存在一个变量中,并根据需要进行分配:

PATH = $ TEMP_PATH_1; #命令…; \ n PATH = $ TEMP_PATH_2; #命令等…;

可能不是你在想什么。 bash / unix中的一些相关命令是:

pushd popd cd ls#也许l -1A单列; 找到grep哪个#可以确认该文件是你认为它来自哪里的; env类型

..所有这一切,一般都有一些关于PATH或目录。 文本修改部分可以通过多种方式完成!

无论选择什么解决方案将有4个部分:

1)按原样获取路径2)解码路径以找到需要更改的部分3)确定需要哪些更改/整合这些更改4)验证/最终集成/设置变量

与dj_segfault的回答一致 ,我在脚本中添加了/ prepend可能被多次执行的环境变量:

 ld_library_path=${ORACLE_HOME}/lib LD_LIBRARY_PATH=${LD_LIBRARY_PATH//${ld_library_path}?(:)/} export LD_LIBRARY_PATH=${ld_library_path}${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}} 

使用相同的技术删除,替换或操作PATH中的条目是微不足道的,因为shell参数扩展的文件名扩展模式匹配和模式列表支持。