有没有人写过bash函数来仅在$ PATH不存在的情况下才将目录添加到$ PATH?

我通常使用类似以下内容的方法添加到PATH:

export PATH=/usr/local/mysql/bin:$PATH


如果我在.bash_profile中构造PATH,则除非我所在的会话是登录会话,否则不会读取它-并非总是如此。如果我在.bashrc中构造我的PATH,那么它将与每个子Shell一起运行。因此,如果启动终端窗口,然后运行屏幕,然后运行Shell脚本,则会得到:

$ echo $PATH
/usr/local/mysql/bin:/usr/local/mysql/bin:/usr/local/mysql/bin:....


我将尝试构建一个名为add_to_path()的bash函数仅在目录不存在时添加目录。但是,如果有人已经写(或发现)了这样的东西,我就不会花时间在上面。

评论

有关可以提供帮助的一些基础结构,请参见stackoverflow.com/questions/273909/…。

unix.stackexchange.com/questions/4965/…

如果您将问题归结为“仅在尚不存在的情况下添加”,那么当插入的项目在开始时很重要但并没有出现在那儿的时候,您将感到非常惊讶。更好的方法是插入元素,然后删除重复项,因此,如果新条目已经存在,则将其有效地移到开头。

#1 楼

在我的.bashrc中:

pathadd() {
    if [ -d "" ] && [[ ":$PATH:" != *"::"* ]]; then
        PATH="${PATH:+"$PATH:"}"
    fi
}


请注意,PATH应该已经被标记为已导出,因此不需要重新导出。这会在添加目录之前检查该目录是否存在&是一个目录,您可能不需要在意它。

此外,这会将新目录添加到路径的末尾;放在开头,请使用PATH="${PATH:+":$PATH"}"而不是上面的PATH=行。

评论


我在乎。

–丹尼斯·威廉姆森
09年9月12日下午3:30

@Neil:它确实起作用,因为它与“:$ PATH:”进行比较,而不仅仅是“ $ PATH”

–戈登·戴维森(Gordon Davisson)
13年4月5日在21:43

@GordonDavisson:很抱歉,我的测试是错误的,您是正确的。

–尼尔
2013年4月5日23:06

@GordonDavisson大括号中内容的含义是什么。我似乎无法解决“ $ {PATH:+” $ PATH:”} $ 1“

–boatcoder
2013年12月21日,0:21



@ Mark0978:这就是我为bukzor指出的问题所做的工作。 $ {variable:+ value}表示检查变量是否已定义并且具有非空值,以及是否给出评估值的结果。基本上,如果PATH以空白开头,则将其设置为“ $ PATH:$ 1”;如果为空白,则将其设置为“ $ 1”(注意缺少冒号)。

–戈登·戴维森(Gordon Davisson)
2013年12月21日在1:47



#2 楼

扩展Gordon Davisson的答案,它支持多个参数。

pathappend() {
  for ARG in "$@"
  do
    if [ -d "$ARG" ] && [[ ":$PATH:" != *":$ARG:"* ]]; then
        PATH="${PATH:+"$PATH:"}$ARG"
    fi
  done
}


,因此您可以进行pathappend path1 path2 path3 ...

用于前置,

pathprepend() {
  for ((i=$#; i>0; i--)); 
  do
    ARG=${!i}
    if [ -d "$ARG" ] && [[ ":$PATH:" != *":$ARG:"* ]]; then
        PATH="$ARG${PATH:+":$PATH"}"
    fi
  done
}


与pathappend类似,您可以执行

pathprepend path1 path2 path3 ...

评论


这很棒!我做了一个小小的改变。对于“ pathprepend”功能,反向读取参数很方便,因此您可以说,例如pathprepend P1 P2 P3并以PATH = P1:P2:P3结尾。要获得此行为,请将ARG中的$$更改为for((i = $#; i> 0; i--));做ARG = $ {!i}

–ishmael
2014年8月15日在21:07



感谢@ishmael,好的建议,我编辑了答案。我知道您的评论已有两年多了,但此后我再也没有回来。我必须弄清楚如何使堆栈交换电子邮件进入我的收件箱!

– Guillaume Perrault-Archambault
16 Dec 15'在23:30

pathprepend使用以下代码给我一个严重的替换错误:github.com/ajr-dev/dotfiles/blob/…

–ajr-dev
20 Dec 15'在17:27

#3 楼

这是我对这个问题的回答,结合道格·哈里斯(Doug Harris)函数的结构。它使用Bash正则表达式:

add_to_path ()
{
    if [[ "$PATH" =~ (^|:)""(:|$) ]]
    then
        return 0
    fi
    export PATH=:$PATH
}


评论


这仅对我有效,仅使用$ 1而不是$ {1}

–安德烈(Andrei)
2012年9月5日19:25

@Andrei:是的,在这种情况下不需要大括号。我不确定为什么要包括它们。

–丹尼斯·威廉姆森
2012年9月5日19:31

#4 楼

将此内容添加到选定答案的注释中,但是注释似乎不支持PRE格式,因此请在此处添加答案:

@ gordon-davisson我不是非常喜欢不必要的引号&级联。假设您使用的bash版本> = 3,则可以使用bash的内置正则表达式执行以下操作:

pathadd() {
    if [ -d "" ] && [[ ! $PATH =~ (^|:)(:|$) ]]; then
        PATH+=:
    fi
}


这样可以正确处理以下情况:目录或PATH。关于bash的内置正则表达式引擎是否足够慢,这可能会比您的版本所执行的字符串连接和内插效率低,但还是有一个问题,但是从某种意义上来说,它对我而言感觉更干净。

评论


注释仅支持使用反引号进行格式设置,但是您没有任何不错的段落控件。

–boatcoder
13年9月24日在21:44

这将添加项放在最后。通常希望添加到开头以覆盖现有位置。

–丹尼斯·威廉姆森
2014年11月20日在21:02

@DennisWilliamson这是一个公平的观点,尽管我不建议您将其作为默认行为。不难弄清楚如何更改为前置。

–克里斯托弗·史密斯(Christopher Smith)
2014年12月30日在9:48

@ChristopherSmith-re:不必要的引用意味着您提前知道$ PATH不为空。无论路径是否为null,“ $ PATH”都可以。类似地,如果$ 1包含可能会使命令解析器混乱的字符。将正则表达式放在引号“(^ |:)$ 1(:| $)”中可以防止这种情况。

–杰西·奇斯霍尔姆(Jesse Chisholm)
2015年11月10日在21:17



@JesseChisholm:实际上,我相信Christopher的观点是[[和]]之间的规则是不同的。我宁愿引用所有可能需要引用的内容,除非这会导致失败,但我相信他是对的,并且$ PATH确实不需要引用。另一方面,在我看来,您大约是1美元。

–斯科特
17年8月22日在18:22

#5 楼

idempotent_path_prepend ()
{
    PATH=${PATH//":"/} #delete any instances in the middle or at the end
    PATH=${PATH//":"/} #delete any instances at the beginning
    export PATH=":$PATH" #prepend to beginning
}


当您需要$ HOME / bin在$ PATH的开头恰好出现一次且无其他地方时,请勿接受任何替代。

评论


谢谢,这是一个很好的解决方案,但是我发现必须使用PATH = $ {PATH /“ ...而不是PATH = $ {PATH //” ...才能使其正常工作。

– Mark Booth
13-10-25在11:01

双斜杠形式应匹配任意多个匹配项;单斜杠只匹配第一个(在bash手册页中搜索“模式替换”)。不知道为什么它不起作用...

– andybuckley
13-10-27在23:21

在$ 1是唯一项(无冒号)的特殊情况下,此操作将失败。条目变为两倍。

–丹尼斯·威廉姆森
2014年11月20日下午21:05

正如PeterS6g所指出的那样,它也会过于激进地删除。

–丹尼斯·威廉姆森
2014年11月20日在21:13

#6 楼

这是我的(我相信它是由我的旧实验室的系统管理员奥斯卡写于多年前的,所有人都应该相信他),它存在于我的bashrc中已有很长时间了。它具有允许您根据需要添加或添加新目录的附加好处:

pathmunge () {
        if ! echo $PATH | /bin/egrep -q "(^|:)($|:)" ; then
           if [ "" = "after" ] ; then
              PATH=$PATH:
           else
              PATH=:$PATH
           fi
        fi
}


用法:

$ echo $PATH
/bin/:/usr/local/bin/:/usr/bin
$ pathmunge /bin/
$ echo $PATH
/bin/:/usr/local/bin/:/usr/bin
$ pathmunge /sbin/ after
$ echo $PATH
/bin/:/usr/local/bin/:/usr/bin:/sbin/


#7 楼

这是另一种解决方案,具有删除冗余整数的附加优点:

function pathadd {
    PATH=:$PATH
    PATH=${PATH//:/}
}


该函数的单个参数位于PATH之前,并且是该函数的第一个实例字符串从现有路径中删除。换句话说,如果目录已经在路径中,则将其提升到最前面,而不是作为重复目录添加。前面的冒号,然后将新条目添加到现有路径的前面,并删除该条目。最后一部分使用bash的${var//pattern/sub}表示法执行;有关更多详细信息,请参见bash手册。

评论


好主意;实施有缺陷。考虑一下,如果您的PATH中已经有/ home / robert并添加了/ home / rob,那么会发生什么情况。

–斯科特
17年8月22日在20:35

#8 楼

首先,我喜欢@Russell的解决方案,但是有一个小错误:如果您尝试将类似“ / bin”的东西放在“ / sbin:/ usr / bin:/ var / usr / bin:/ usr / local”的路径中/ bin:/ usr / sbin“替换” / bin:“ 3次(当它根本不匹配时)。将针对此问题的修复与@ gordon-davisson的附加解决方案相结合,我得到的是:

path_prepend() {
    if [ -d "" ]; then
        PATH=${PATH//"::"/:} #delete all instances in the middle
        PATH=${PATH/%":"/} #delete any instance at the end
        PATH=${PATH/#":"/} #delete any instance at the beginning
        PATH="${PATH:+":$PATH"}" #prepend  or if $PATH is empty set to 
    fi
}


#9 楼

像下面这样的简单别名应该可以解决问题:

alias checkInPath="echo $PATH | tr ':' '\n' | grep -x -c "


它所做的只是将:字符上的路径拆分,并将每个组件与您传入的参数进行比较。 。grep检查是否有完整的行匹配,并打印出计数。

示例用法:

$ checkInPath "/usr/local"
1
$ checkInPath "/usr/local/sbin"
1
$ checkInPath "/usr/local/sbin2"
0
$ checkInPath "/usr/local/" > /dev/null && echo "Yes" || echo "No"
No
$ checkInPath "/usr/local/bin" > /dev/null && echo "Yes" || echo "No"
Yes
$ checkInPath "/usr/local/sbin" > /dev/null && echo "Yes" || echo "No"
Yes
$ checkInPath "/usr/local/sbin2" > /dev/null && echo "Yes" || echo "No"
No


用addToPath替换echo命令或一些类似的别名/功能。

评论


使用“ grep -x”可能比我在bash函数中放入的循环更快。

–道格·哈里斯(Doug Harris)
09年9月11日在18:04

#10 楼

请参阅如何避免在csh中复制路径变量?在StackOverflow上获得此问题的一组答案。

#11 楼

这样可以正常工作:

if [[ ":$PATH:" != *":/new-directory:"* ]]; then PATH=${PATH}:/new-directory; fi


#12 楼

这是我的想法:

add_to_path ()
{
    path_list=`echo $PATH | tr ':' ' '`
    new_dir=
    for d in $path_list
    do
        if [ $d == $new_dir ]
        then
            return 0
        fi
    done
    export PATH=$new_dir:$PATH
}


现在在.bashrc中,我有:

add_to_path /usr/local/mysql/bin



更新了以下版本,其中有关于我的原件将不处理带空格的目录的注释(由于此问题使我指向使用IFS):

add_to_path ()
{
    new_dir=
    local IFS=:
    for d in $PATH
    do
        if [[ "$d" == "$new_dir" ]]
        then
            return 0
        fi
    done
    export PATH=$new_dir:$PATH
}


评论


如果PATH中已有目录名称包含空格,* 、?或[…],则此操作可能会失败。

–斯科特
17年8月22日在18:06

好点……但是我是一个老派的Linux专家,永远不会使用带有空格的路径:-)

–道格·哈里斯(Doug Harris)
17年8月22日在19:09

对您有好处,因为它们不会用名称中的空格创建内容。如果编写无法处理它们的代码,您会感到羞耻。作为“老派Linux专家”与它有什么关系? Windoze可能已经普及了这个想法(感谢文档和设置以及程序文件),但是自从Windoze出现之前,Unix就支持包含空格的路径名。

–斯科特
17年8月22日在20:28

#13 楼

令我惊讶的是,还没有人提及它,但您可以使用readlink -f将相对路径转换为绝对路径,
然后将其添加到PATH中。

例如,为了改善Guillaume Perrault-Archambault的答案,

pathappend() {
  for ARG in "$@"
  do
    if [ -d "$ARG" ] && [[ ":$PATH:" != *":$ARG:"* ]]; then
        PATH="${PATH:+"$PATH:"}$ARG"
    fi
  done
}





pathappend() {
    for ARG in "$@"
    do
        if [ -d "$ARG" ] && [[ ":$PATH:" != *":$ARG:"* ]]
        then
            if ARGA=$(readlink -f "$ARG")               #notice me
            then
                if [ -d "$ARGA" ] && [[ ":$PATH:" != *":$ARGA:"* ]]
                then
                    PATH="${PATH:+"$PATH:"}$ARGA"
                fi
            else
                PATH="${PATH:+"$PATH:"}$ARG"
            fi
        fi
    done
}


1 。基础知识–这有什么用?

readlink -f命令将(除其他事项外)将相对路径转换为绝对路径。
这允许您执行类似

$ cd /path/to/my/bin/dir
$ pathappend .
$ echo "$PATH"
<your_old_path>:/path/to/my/bin/dir


2的操作。为什么我们要两次测试是否在PATH中?

好,请考虑上面的示例。
如果用户第二次从pathappend .目录中说出/path/to/my/bin/dir
,则
ARG将是.
当然,.中不会出现PATH
,然后将ARGA设置为/path/to/my/bin/dir .的绝对路径),该值已经在PATH中。
所以我们需要避免再次将/path/to/my/bin/dir添加到PATH

也许更重要的是,
readlink的主要用途,顾名思义,是
查看一个符号链接,并读出它包含的路径名(即指向)。
例如:

$ ls -ld /usr/lib/perl/5.14
-rwxrwxrwx  1   root   root    Sep  3  2015 /usr/lib/perl/5.14 -> 5.14.2
$ readlink /usr/lib/perl/5.14
5.14.2
$ readlink -f /usr/lib/perl/5.14
/usr/lib/perl/5.14.2


现在,如果您说pathappend /usr/lib/perl/5.14
并且您的路径中已经有/usr/lib/perl/5.14
没关系;我们可以保持原样。
但是,如果/usr/lib/perl/5.14还不在您的路径中,
我们调用readlink并获取ARGA = /usr/lib/perl/5.14.2
,然后将其添加到PATH中。
等一下-如果您已经说过pathappend /usr/lib/perl/5.14
那么您的路径中已经有了/usr/lib/perl/5.14.2,再次,
我们需要检查一下以避免将其添加到PATH中时间。

3。 if ARGA=$(readlink -f "$ARG")有何处理?

如果不清楚,此行将测试readlink是否成功。
这是好的防御性编程实践。
如果我们要使用命令m
的输出作为命令n的一部分(其中m 命令m是否失败并以某种方式处理。
我认为readlink不太可能会失败-但是,正如如何在OSX和其他地方检索任意文件的绝对路径中所述的那样,readlink是GNU发明。
在POSIX中未指定,因此在Mac OS,Solaris和其他非Linux Unix中的可用性令人怀疑。
(实际上,我只读了一条评论,说
readlink -f似乎在Mac OS X 10.11.6上不起作用,但是realpath可以开箱即用。”
>因此,如果您使用的是没有readlink的系统,
readlink -f无法正常工作的系统,
您可以修改此脚本以使用realpath。)
通过安装安全网,我们使我们的代码更具可移植性。

当然,如果您使用的系统没有readlink(或realpath),
您不想执行pathappend .

第二次-d测试([ -d "$ARGA" ])确实很必要。
我想不出任何情况
其中$ARG是目录而readlink成功,但
$ARGA不是目录。
我只是复制并粘贴了第一个if语句以创建第三个语句,
我出于懒惰而将-d测试留在那里。

4。还有其他意见吗?

是的。
像这里的其他许多答案一样,
该函数测试每个参数是否为目录,如果是,则对其进行处理,如果不是,则将其忽略。
如果仅在“ pathappend”文件(例如..bash_profile)文件中使用.bashrc
和其他脚本,则这可能(也可能不)。
但是,正如上面答案所示,
以交互方式使用它是完全可行的。
如果这样做,您会很困惑

$ pathappend /usr/local/nysql/bin
$ mysql
-bash: mysql: command not found



您是否注意到我在nysql命令中说的是pathappend
,而不是mysql
而那个pathappend没说什么;
它只是默默地忽略了错误的论点?


我上面说过,处理错误是个好习惯。
这里是一个例子:

pathappend() {
    for ARG in "$@"
    do
        if [ -d "$ARG" ]
        then
            if [[ ":$PATH:" != *":$ARG:"* ]]
            then
                if ARGA=$(readlink -f "$ARG")           #notice me
                then
                    if [[ ":$PATH:" != *":$ARGA:"* ]]
                    then
                        PATH="${PATH:+"$PATH:"}$ARGA"
                    fi
                else
                    PATH="${PATH:+"$PATH:"}$ARG"
                fi
            fi
        else
            printf "Error: %s is not a directory.\n" "$ARG" >&2
        fi
    done
}


评论


(1)您应该添加引号:readlink -f“ $ ARG”。 (2)我不知道为什么会发生这种情况(尤其是在-d“ $ ARG”测试成功之后),但是您可能想测试readlink是否失败。 (3)您似乎在忽略readlink的主要功能-将符号链接名称映射到“真实文件名”。如果(例如)/ bin是/ bin64的符号链接,则对pathappend / bin的重复调用可能导致PATH表示…:/ bin64:/ bin64:/ bin64:/ bin64:…。您可能(还)应该检查$ ARG的新值是否已经在PATH中。

–斯科特
17年8月22日在16:58

(1)观察良好,我已将其修复。 (2)在什么情况下readlink会失败?假定路径是正确的,并且它指向有效位置。 (3)我不确定是什么决定了readlink的主要功能,我相信unix文件系统中的大多数(如果不是全部?)路径可以分为两种类型的链接:符号链接和硬链接。至于重复的路径条目,您是对的,但是我的答案并不是要添加它(因为我已经注意到其他答案已经提到了它)。我要添加另一个答案的唯一原因是贡献一些我认为尚未贡献的内容

– qwertyzw
17年8月22日在17:16



(2)我的意思是,如果至少隐含/暗示了命令的名称,那么readlink中的“链接”可以指硬链接或软链接。但是,您是正确的:man readlink说“ readlink-打印已解析的符号链接或规范文件名”。在我的示例中,我认为可以将其视为规范文件名。正确?

– qwertyzw
17年8月22日在17:22



(1)对于了解符号链接的人来说,readlink的名称明确表示其目的-它查看符号链接并读出其包含的路径名(即指向)。 (2)好,我说过,我不知道为什么readlink会失败。我的基本观点是,如果脚本或函数包含多个命令,并且如果commandm失败(其中m
–斯科特
17年8月26日在20:36

(续)...通过诊断中止脚本/功能。假设,如果(a)在调用test和readlink的调用之间目录被重命名或删除(通过另一个过程),或者(b)如果/ usr / bin / readlink被删除(或损坏),则readlink可能会失败。 (3)您似乎错过了我的意思。我不鼓励您重复其他答案;我是说,通过检查原始ARG(从命令行)是否已经在PATH中,而不是对新的ARG(readlink的输出)重复检查,您的答案是不完整的,因此是不正确的。 …(续)

–斯科特
17年8月26日在20:36

#14 楼

function __path_add(){  
if [ -d "" ] ; then  
    local D=":${PATH}:";   
    [ "${D/::/:}" == "$D" ] && PATH="$PATH:";  
    PATH="${PATH/#:/}";  
    export PATH="${PATH/%:/}";  
fi  
}  


#15 楼

我的版本对空路径并坚持路径有效和目录的注意要比此处发布的要少一些,但我确实发现了大量prepend / append / clean / unique-ify / etc。 shell函数对路径操作很有用。整个状态都在这里:http://pastebin.com/xS9sgQsX
(非常欢迎反馈和改进!)

#16 楼

您可以使用一个perl衬垫:

appendPaths() { # append a group of paths together, leaving out redundancies
    # use as: export PATH="$(appendPaths "$PATH" "dir1" "dir2")
    # start at the end:
    #  - join all arguments with :,
    #  - split the result on :,
    #  - pick out non-empty elements which haven't been seen and which are directories,
    #  - join with :,
    #  - print
    perl -le 'print join ":", grep /\w/ && !$seen{$_}++ && -d $_, split ":", join ":", @ARGV;' "$@"
}


这里是bash:

addToPath() { 
    # inspired by Gordon Davisson, http://superuser.com/a/39995/208059
    # call as: addToPath dir1 dir2
    while (( "$#" > 0 )); do
    echo "Adding  to PATH."
    if [[ ! -d "" ]]; then
        echo " is not a directory.";
    elif [[ ":$PATH:" == *"::"* ]]; then
        echo " is already in the path."
    else
            export PATH="${PATH:+"$PATH:"}" # ${x:-defaultIfEmpty} ${x:+valueIfNotEmpty}
    fi
    shift
    done
}


#17 楼

如果未提供任何内容,我对Gordon Davisson的答案进行了少许修改以使用当前目录。因此,您只需从要添加到PATH的目录中执行padd

padd() {
  current=`pwd`
  p=${1:-$current}
  if [ -d "$p" ] && [[ ":$PATH:" != *":$p:"* ]]; then
      PATH="$p:$PATH"
  fi
}


#18 楼

您可以检查是否已设置自定义变量,否则可以对其进行设置,然后添加新条目:

if [ "$MYPATHS" != "true" ]; then
    export MYPATHS="true"
    export PATH="$PATH:$HOME/bin:"

    # java stuff
    export JAVA_HOME="$(/usr/libexec/java_home)"
    export M2_HOME="$HOME/Applications/apache-maven-3.3.9"
    export PATH="$JAVA_HOME/bin:$M2_HOME/bin:$PATH"

    # etc...
fi


当然,如果通过添加这些条目,这些条目仍然可以重复另一个脚本,例如/etc/profile

#19 楼

此脚本允许您在$PATH的末尾添加

PATH=path2; add_to_PATH after path1 path2:path3
echo $PATH
path2:path1:path3


或在$PATH的开头添加

PATH=path2; add_to_PATH before path1 path2:path3
echo $PATH
path1:path3:path2



# Add directories to $PATH iff they're not already there
# Append directories to $PATH by default
# Based on https://unix.stackexchange.com/a/4973/143394
# and https://unix.stackexchange.com/a/217629/143394
add_to_PATH () {
  local prepend  # Prepend to path if set
  local prefix   # Temporary prepended path
  local IFS      # Avoid restoring for added laziness

  case  in
    after)  shift;; # Default is to append
    before) prepend=true; shift;;
  esac

  for arg; do
    IFS=: # Split argument by path separator
    for dir in $arg; do
      # Canonicalise symbolic links
      dir=$({ cd -- "$dir" && { pwd -P || pwd; } } 2>/dev/null)
      if [ -z "$dir" ]; then continue; fi  # Skip non-existent directory
      case ":$PATH:" in
        *":$dir:"*) :;; # skip - already present
        *) if [ "$prepend" ]; then
           # ${prefix:+$prefix:} will expand to "" if $prefix is empty to avoid
           # starting with a ":".  Expansion is "$prefix:" if non-empty.
            prefix=${prefix+$prefix:}$dir
          else
            PATH=$PATH:$dir  # Append by default
          fi;;
      esac
    done
  done
  [ "$prepend" ] && PATH=$prefix:$PATH
}


#20 楼

这是一种符合POSIX的方式。

# USAGE: path_add [include|prepend|append] "dir1" "dir2" ...
#   prepend: add/move to beginning
#   append:  add/move to end
#   include: add to end of PATH if not already included [default]
#          that is, don't change position if already in PATH
# RETURNS:
# prepend:  dir2:dir1:OLD_PATH
# append:   OLD_PATH:dir1:dir2
# If called with no paramters, returns PATH with duplicate directories removed
path_add() {
    # use subshell to create "local" variables
    PATH="$(path_unique)"
    export PATH="$(path_add_do "$@")"
}

path_add_do() {
    case "" in
    'include'|'prepend'|'append') action=""; shift ;;
    *)                            action='include'   ;;
    esac

    path=":$PATH:" # pad to ensure full path is matched later

    for dir in "$@"; do
        #       [ -d "$dir" ] || continue # skip non-directory params

        left="${path%:$dir:*}" # remove last occurrence to end

        if [ "$path" = "$left" ]; then
            # PATH doesn't contain $dir
            [ "$action" = 'include' ] && action='append'
            right=''
        else
            right=":${path#$left:$dir:}" # remove start to last occurrence
        fi

        # construct path with $dir added
        case "$action" in
            'prepend') path=":$dir$left$right" ;;
            'append')  path="$left$right$dir:" ;;
        esac
    done

    # strip ':' pads
    path="${path#:}"
    path="${path%:}"

    # return
    printf '%s' "$path"
}

# USAGE: path_unique [path]
# path - a colon delimited list. Defaults to $PATH is not specified.
# RETURNS: `path` with duplicated directories removed
path_unique() {
    in_path=${1:-$PATH}
    path=':'

    # Wrap the while loop in '{}' to be able to access the updated `path variable
    # as the `while` loop is run in a subshell due to the piping to it.
    # https://stackoverflow.com/questions/4667509/shell-variables-set-inside-while-loop-not-visible-outside-of-it
    printf '%s\n' "$in_path" \
    | /bin/tr -s ':' '\n'    \
    | {
            while read -r dir; do
                left="${path%:$dir:*}" # remove last occurrence to end
                if [ "$path" = "$left" ]; then
                    # PATH doesn't contain $dir
                    path="$path$dir:"
                fi
            done
            # strip ':' pads
            path="${path#:}"
            path="${path%:}"
            # return
            printf '%s\n' "$path"
        }
}


它是由Guillaume Perrault-Archambault对这个问题的回答和mike511在这里的回答所抄袭的。

更新2017-11-23:修复了每个@Scott的错误

评论


我打算为此提供一个命令行选项,以在默认和默认之间进行选择。但是后来我想到:这是很多有些神秘的代码,没有任何解释。 (事实上​​,您有两个函数,一个函数更改PATH并回显其新值,另一个函数捕获输出并将其再次分配给PATH,这是不必要的复杂性。)…(续)

–斯科特
17年8月23日在21:49

(续)……然后我注意到链接有误。 (而且我不确定您为什么要责怪那些家伙;您似乎并没有从他们的答案中复制太多内容。)然后我注意到代码是错误的。我想它可以很好地维持格式正确的PATH,但是不能保证它已经格式正确,尤其是在您未启用/ etc / profile的情况下。您尝试添加到PATH的目录可能已经存在多个目录,并且您的代码因此而阻塞。 …(续)

–斯科特
17年8月23日在21:49

(续)…例如,如果您尝试将/ a / ck放在/ b:/ a / ck:/ tr:/ a / ck的前面,您将获得/ a / ck:/ b:/ a / ck: / tr:/ tr:/ a / ck。

–斯科特
17年8月23日在21:49