在 Bash 中从 $PATH 变量中删除路径的最优雅的方法是什么?
或者更一般地说,如何从 Bash 环境变量中以冒号分隔的列表中删除项目?
我以为几年前我已经看到了一种简单的方法,使用更高级的 Bash 变量扩展形式,但如果是这样,我已经忘记了它。 谷歌的快速搜索令人惊讶地发现很少有相关结果,而且没有一个我称之为“简单”或“优雅”的结果。 例如,分别使用 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)
没有什么直接的存在吗? Bash 中是否有类似于 split() 函数的东西?
更新:
看来我需要为我故意含糊的问题道歉; 我对解决特定用例更感兴趣,而不是引发良好的讨论。 幸运的是,我得到了!
这里有一些非常聪明的技巧。 最后,我将以下三个函数添加到我的工具箱中。 神奇的事情发生在 path_remove 中,这很大程度上基于 Martin York 对 awk 的 RS 变量的巧妙使用。
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 删除尾随冒号。 不过,考虑到马丁解决方案的其余部分是多么简单,我非常愿意接受它!
Or more generally, how do I remove an item from a colon-separated list in a Bash environment variable?
I thought I had seen a simple way to do this years ago, using the more advanced forms of Bash variable expansion, but if so I've lost track of it. A quick search of Google turned up surprisingly few relevant results and none that I would call "simple" or "elegant". For example, two methods using sed and awk, respectively:
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)
Does nothing straightforward exist? Is there anything analogous to a split() function in Bash?
Update:
It looks like I need to apologize for my intentionally-vague question; I was less interested in solving a specific use-case than in provoking good discussion. Fortunately, I got it!
There are some very clever techniques here. In the end, I've added the following three functions to my toolbox. The magic happens in path_remove, which is based largely on Martin York's clever use of awk
's RS variable.
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/:$//'`; }
The only real cruft in there is the use of sed
to remove the trailing colon. Considering how straightforward the rest of Martin's solution is, though, I'm quite willing to live with it!
Related question:How do I manipulate $PATH elements in shell scripts?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(30)
我的肮脏黑客:
My dirty hack:
用 awk 一分钟:
编辑:它对下面的评论作出回应:
编辑以回应安全问题:(与问题无关)
这将删除通过删除最后一个条目留下的任何尾随冒号,这将有效地添加
.< /code> 到你的路径。
A minute with awk:
Edit: It response to comments below:
Edit in response to security problem: (that is not relevant to the question)
This removes any trailing colons left by deleting the last entries, which would effectively add
.
to your path.由于替换的最大问题是最终情况,那么如何使最终情况与其他情况没有不同呢? 如果路径的开头和结尾已经有冒号,我们可以简单地搜索用冒号包裹的所需字符串。 事实上,我们可以轻松地添加这些冒号并随后将其删除。
纯粹的狂欢:)。
Since the big issue with substitution is the end cases, how about making the end cases no different to the other cases? If the path already had colons at the start and end, we could simply search for our desired string wrapped with colons. As it is, we can easily add those colons and remove them afterwards.
Pure bash :).
这是我可以设计的最简单的解决方案:
上面的示例将删除 $PATH 中包含“usr”的任何元素。 您可以将“*usr*”替换为“/home/user/bin”以仅删除该元素。
更新每sschuberth
即使我认为
$PATH
中有空格是一个可怕的想法,这里有一个处理它的解决方案:或者
Here's the simplest solution i can devise:
The above example will remove any element in $PATH that contains "usr". You can replace "*usr*" with "/home/user/bin" to remove just that element.
update per sschuberth
Even though i think spaces in a
$PATH
are a horrible idea, here's a solution that handles it:or
尽管当前已接受并且评分最高的答案,不会在PATH中添加不可见字符,并且可以应对包含空格的路径:
就我个人而言,我也觉得这很容易阅读/理解,并且只涉及常用命令而不是使用 awk。
Here's a one-liner that, despite the current accepted and highest rated answers, does not add invisible characters to PATH and can cope with paths that contain spaces:
Personally, I also find this easy to read / understand, and it only involves common commands instead of using awk.
这是一个解决方案:
IFS
,删除
PATH
中出现的所有参数。Here is a solution that:
IFS
,removes all occurrences of the argument in
PATH
.到目前为止,我发现的最好的纯bash选项如下:
这是基于不太正确的答案到如果超级用户上尚不存在,请将目录添加到 $PATH,修复评论中提到的问题。
显然,如果您不需要解释性注释,可以将其制成单行函数。
The best pure bash option I have found so far is the following:
This is based on the not quite correct answer to Add directory to $PATH if it's not already there over on Superuser, fixing issues mentioned in comments.
Obviously this can be made into a single line function if you don't want the explanatory comments.
我刚刚使用了 bash 发行版中的函数,这些函数显然自 1991 年以来就已经存在了。这些函数仍然位于 Fedora 上的 bash-docs 包中,并且曾经在
/etc/profile
中使用,但没有更多了……I've just been using the functions in the bash distribution, that have been there apparently since 1991. These are still in the bash-docs package on Fedora, and used to be used in
/etc/profile
, but no more...从我的 .bashrc 文件中挖出它。
当你使用 PATH 时,它会丢失,awk/sed/grep 变得不可用:-)
Dug it out from my .bashrc file.
When you play around with PATH, and it gets lost, awk/sed/grep becomes unavailable :-)
Linux from Scratch 在
/etc/profile
中定义了三个 Bash 函数:Ref: http://www.linuxfromscratch.org/blfs/view/svn/postlfs/profile.html
Linux from Scratch defines three Bash functions in
/etc/profile
:Ref: http://www.linuxfromscratch.org/blfs/view/svn/postlfs/profile.html
我确实在此处写了答案 (也使用 awk)。 但我不确定这就是你要找的吗? 它至少对我来说看起来很清楚它的作用,而不是试图融入一行。 不过,对于一个简单的单行,只会删除一些东西,我建议
替换为
或(更短但可读性较差)
无论如何,对于同一问题以及一大堆有用的答案,请参阅 此处。
I did write an answer to this here (using awk too). But i'm not sure that's what you are looking for? It at least looks clear to me what it does, instead of trying to fit into one line. For a simple one liner, though, that only removes stuff, i recommend
Replacing is
or (shorter but less readable)
Anyway, for the same question, and a whole lot of useful answers, see here.
还有什么比 awk 更优雅的呢?
Python! 这是一个更具可读性和可维护性的解决方案,并且很容易检查它是否真正按照您的要求进行操作。
假设您要删除第一个路径元素?
(不是从
echo
进行管道传输,os.getenv['PATH']
会更短一些,并且提供与上面相同的结果,但我担心Python 可能会对该环境变量执行某些操作,因此最好直接从您关心的环境中通过管道传输它。)类似地,从末尾删除:
要使这些可重用的 shell 函数,您可以将其粘贴在 .bashrc 中文件:
What's more elegant than awk?
Python! It's a more readable and maintainable solution, and it is easy to inspect to see that it's really doing what you want.
Say you want to remove the first path element?
(Instead of piping from
echo
,os.getenv['PATH']
would be a little shorter, and provided the same result as the above, but I'm worried that Python might do something with that environment variable, so it's probably best to pipe it directly from the environment you care about.)Similarly to remove from the end:
To make these reusable shell functions that you can, for example, stick in your .bashrc file:
好吧,在 bash 中,因为它支持正则表达式,我只需这样做:
Well, in bash, as it supports regular expression, I would simply do :
我喜欢@BenBlank对其原始问题的更新中显示的三个函数。 为了概括它们,我使用 2 参数形式,它允许我设置 PATH 或任何其他我想要的环境变量:
使用示例:
请注意,我还添加了一些引号,以允许正确处理包含空格的路径名。
I like the three functions shown in @BenBlank's update to his original question. To generalize them, I use a 2-argument form, which allows me to set PATH or any other environment variable I want:
Examples of use:
Note that I also added some quotation marks to allow for the proper processing of pathnames that contain spaces.
使这个问题变得烦人的是第一个和最后一个元素之间的栅栏情况。 通过更改 IFS 并使用数组可以优雅地解决该问题,但我不知道一旦路径转换为数组形式如何重新引入冒号。
这是一个稍微不太优雅的版本,仅使用字符串操作从
$PATH
中删除一个目录。 我已经测试过了。What makes this problem annoying are the fencepost cases among first and last elements. The problem can be elegantly solved by changing IFS and using an array, but I don't know how to re-introduce the colon once the path is converted to array form.
Here is a slightly less elegant version that removes one directory from
$PATH
using string manipulation only. I have tested it.是的,例如,在 PATH 末尾放置一个冒号可以使删除路径变得不那么笨拙和方便。 容易出错。
Yes, putting a colon at the end of PATH, for example, makes removing a path a bit less clumsy & error-prone.
如果您担心删除 $PATH 中的重复项,恕我直言,最优雅的方法就是不要首先添加它们。 1 行:
$folder 可以替换为任何内容,并且可以包含空格(“/home/user/my Documents”)
If you are concerned about removing duplicates in $PATH, the most elegant way, IMHO, would be not to add them in the first place. In 1 line:
$folder can be be replaced by anything, and may contain spaces ("/home/user/my documents")
迄今为止我发现的最优雅的纯 bash 解决方案:
The most elegant pure bash solution I've found to date:
大多数其他建议的解决方案仅依赖于字符串匹配,并且不考虑包含特殊名称的路径段,例如
.
、..
或~. 下面的 bash 函数解析其参数和路径段中的目录字符串,以查找逻辑目录匹配以及字符串匹配。
测试:
Most of the other suggested solutions rely only on string matching and don't take into account path segments containing special names like
.
,..
, or~
. The bash function below resolves directory strings in its argument and in path segments to find logical directory matches as well as string matches.Test:
我知道这个问题询问的是 BASH,每个人都应该更喜欢,但由于我喜欢对称性,有时需要使用“csh”,所以我构建了与“path_prepend()”、“path_append()”和“path_remove”等效的内容()”上面的优雅解决方案。
要点是“csh”没有函数,所以我在我的个人 bin 目录中放置了一些类似于函数的 shell 脚本。 我为 SOURCE 这些脚本创建别名,以更改指定的环境变量。
〜/bin/_path_remove.csh:
〜/bin/_path_append.csh:
〜/bin/_path_prepend.csh:
〜/bin/.cshrc:
您可以像这样使用它们...
I know this question asks about BASH, which everyone should prefer, but since I enjoy symmetry and sometimes I'm required to use "csh", I built the equivalent to the "path_prepend()", "path_append()" and "path_remove()" elegant solution above.
The gist is that "csh" doesn't have functions, so I put little shell scripts in my personal bin directory that act like the functions. I create aliases to SOURCE those scripts to make the designated environment variable changes.
~/bin/_path_remove.csh:
~/bin/_path_append.csh:
~/bin/_path_prepend.csh:
~/bin/.cshrc:
You can use them like this...
Bash 内置 oneliner(不删除重复):
PATH=${PATH/${PATH/#$DIR:*/$DIR:}/}${PATH/${PATH/*:$DIR*/:$DIR}/}
Bash built-in oneliner (doesn't remove repetition):
PATH=${PATH/${PATH/#$DIR:*/$DIR:}/}${PATH/${PATH/*:$DIR*/:$DIR}/}
由于这往往是一个很大的问题,因为没有优雅的方法,我建议通过重新安排解决方案来避免问题:建立你的路径而不是试图拆除它。
如果我知道您真正的问题背景,我可以更具体。 在此期间,我将使用软件构建作为上下文。
软件构建的一个常见问题是它在某些机器上崩溃,最终是由于某人配置其默认 shell(路径和其他环境变量)的方式造成的。 优雅的解决方案是通过完全指定 shell 环境来使构建脚本免受影响。 对构建脚本进行编码,以根据您控制的组装部分(例如编译器、库、工具、组件等的位置)设置 PATH 和其他环境变量。使每个可配置项都可以单独设置、验证和使用然后在您的脚本中适当使用。
例如,我有一个基于 Maven 的、面向 WebLogic 的 Java 构建,它是我在新雇主处继承的。 构建脚本因脆弱而臭名昭著,我和另一位新员工花了三个星期(不是全职,只是到处,但仍然有很多时间)让它在我们的机器上运行。 一个重要的步骤是我控制了 PATH,以便我准确地知道正在调用哪个 Java、哪个 Maven 以及哪个 WebLogic。 我创建了环境变量来指向每个工具,然后根据这些工具和其他一些工具计算了 PATH。 类似的技术驯服了其他可配置的设置,直到我们最终创建了一个可重现的构建。
顺便说一句,不要使用 Maven,Java 也可以,只有当您绝对需要它的集群时才购买 WebLogic(否则不需要,尤其是不需要它的专有功能)。
最好的祝愿。
Since this tends to be quite problematic, as in there IS NO elegant way, I recommend avoiding the problem by rearranging the solution: build your PATH up rather than attempt to tear it down.
I could be more specific if I knew your real problem context. In the interim, I will use a software build as the context.
A common problem with software builds is that it breaks on some machines, ultimately due to how someone has configured their default shell (PATH and other environment variables). The elegant solution is to make your build scripts immune by fully specifying the shell environment. Code your build scripts to set the PATH and other environment variables based on assembling pieces that you control, such as the location of the compiler, libraries, tools, components, etc. Make each configurable item something that you can individually set, verify, and then use appropriately in your script.
For example, I have a Maven-based WebLogic-targeted Java build that I inherited at my new employer. The build script is notorious for being fragile, and another new employee and I spent three weeks (not full time, just here and there, but still many hours) getting it to work on our machines. An essential step was that I took control of the PATH so that I knew exactly which Java, which Maven, and which WebLogic was being invoked. I created environment variables to point to each of those tools, then I calculated the PATH based on those plus a few others. Similar techniques tamed the other configurable settings, until we finally created a reproducible build.
By the way, don't use Maven, Java is okay, and only buy WebLogic if you absolutely need its clustering (but otherwise no, and especially not its proprietary features).
Best wishes.
与@litb一样,我对问题“How我是否在 shell 脚本中操作 $PATH 元素”,所以我的主要答案就在那里。
bash
和其他 Bourne shell 衍生产品中的“分割”功能是通过字段间分隔符$IFS
完美实现的。 例如,要将位置参数 ($1
,$2
, ...) 设置为 PATH 的元素,请使用:只要没有空格,它就可以正常工作在 $PATH 中。 使其适用于包含空格的路径元素是一项不简单的练习 - 留给感兴趣的读者。 使用 Perl 等脚本语言来处理它可能更简单。
我还有一个脚本
clnpath
,我广泛使用它来设置我的 PATH。 我在“如何避免在 csh 中复制 PATH 变量”。As with @litb, I contributed an answer to the question "How do I manipulate $PATH elements in shell scripts", so my main answer is there.
The 'split' functionality in
bash
and other Bourne shell derivatives is most neatly achieved with$IFS
, the inter-field separator. For example, to set the positional arguments ($1
,$2
, ...) to the elements of PATH, use:It will work OK as long as there are no spaces in $PATH. Making it work for path elements containing spaces is a non-trivial exercise - left for the interested reader. It is probably simpler to deal with it using a scripting language such as Perl.
I also have a script,
clnpath
, which I use extensively for setting my PATH. I documented it in the answer to "How to keep from duplicating PATH variable in csh".这是一个 Perl 语句:
$a
变量获取要删除的路径。s
(替换)和print
命令隐式对$_
变量进行操作。Here's a Perl one-liner:
The
$a
variable gets the path to be removed. Thes
(substitute) andprint
commands implicitly operate on the$_
variable.这里有好东西。
我使用这个是为了从一开始就避免添加欺骗。
Good stuff here.
I use this one to keep from adding dupes in the first place.
启用扩展通配符后,可以执行以下操作:
With extended globbing enabled it's possible to do the following:
扩展的通配符单行(嗯,有点):
似乎没有必要在 1 美元中转义斜线。
Extended globbing one-liner (well, sort of):
There seems no need to escape slashes in $1.
将冒号添加到 PATH 我们还可以执行以下操作:
Adding colons to PATH we could also do something like:
在path_remove_all中(由proxxy):
In path_remove_all (by proxxy):
虽然这是一个非常古老的线程,但我认为这个解决方案可能很有趣:
在这个 博客文章。 我想我最喜欢这个:)
While this is a very old thread, I thought this solution might be of interest:
found it on this blog post. I think I like this one most :)