将目录添加到 $PATH 如果它不存在

Dou*_*ris 149 script bash path

有没有人写了一个 bash 函数来将一个目录添加到 $PATH 中,如果它不存在?

我通常使用以下内容添加到 PATH:

export PATH=/usr/local/mysql/bin:$PATH
Run Code Online (Sandbox Code Playgroud)

如果我在 .bash_profile 中构建我的 PATH,那么它不会被读取,除非我所在的会话是登录会话——这并不总是正确的。如果我在 .bashrc 中构建我的 PATH,那么它会与每个子 shell 一起运行。因此,如果我启动一个终端窗口,然后运行 ​​screen 然后运行一个 shell 脚本,我会得到:

$ echo $PATH
/usr/local/mysql/bin:/usr/local/mysql/bin:/usr/local/mysql/bin:....
Run Code Online (Sandbox Code Playgroud)

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

Gor*_*son 149

从我的 .bashrc:

pathadd() {
    if [ -d "$1" ] && [[ ":$PATH:" != *":$1:"* ]]; then
        PATH="${PATH:+"$PATH:"}$1"
    fi
}
Run Code Online (Sandbox Code Playgroud)

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

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

  • 我在乎。 (30认同)
  • @Mark0978:这就是我为解决 bukzor 指出的问题所做的工作。`${variable:+value}` 表示检查 `variable` 是否被定义并且具有非空值,如果确实给出了对 `value` 求值的结果。基本上,如果 PATH 以非空开头,则将其设置为 `"$PATH:$1"`; 如果它是空白的,则将其设置为`"$1"`(注意缺少冒号)。 (6认同)
  • @Neil:它确实有效,因为它与 `":$PATH:"` 相比,而不仅仅是 `"$PATH"` (5认同)
  • 然后你可以通过在 `.bashrc` 文件的末尾添加这样一行来添加所需的路径: `pathadd "/usr/local/mysql/bin/"` 我知道这对 Linux 用户来说很明显,但它可能会有所帮助那些来自 Windows 背景并且不熟悉 bash 的人。 (4认同)
  • @GordonDavisson:我很抱歉,我的测试错了,你是对的。 (3认同)
  • @GordonDavisson 大括号中的内容有什么意义。我似乎无法解开“`${PATH:+"$PATH:"}`$1” (2认同)
  • 适用于 OS X 的 `bash 3.2`,但我在 POSIX sh 中读到,不支持 [[]]。我是不是太担心了? (2认同)

Gui*_*ult 27

扩展戈登戴维森的答案,这支持多个论点

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

所以你可以做 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
}
Run Code Online (Sandbox Code Playgroud)

类似于pathappend,你可以做

pathprepend path1 path2 path3 ...

  • 这很棒!我做了一个小小的改变。对于 'pathprepend' 函数,可以方便地反向读取参数,因此您可以说,例如,`pathprepend P1 P2 P3` 并以 `PATH=P1:P2:P3` 结尾。要获得此行为,请将 `for ARG in "$@" do` 更改为 `for ((i=$#; i>0; i--)); 做 ARG=${!i}` (3认同)

Den*_*son 15

这是这个问题的回答结合道格哈里斯函数的结构。它使用 Bash 正则表达式:

add_to_path ()
{
    if [[ "$PATH" =~ (^|:)"${1}"(:|$) ]]
    then
        return 0
    fi
    export PATH=${1}:$PATH
}
Run Code Online (Sandbox Code Playgroud)


小智 11

将此放在所选答案的评论中,但评论似乎不支持 PRE 格式,因此在此处添加答案:

@gordon-davisson 我不喜欢不必要的引用和连接。假设您使用的是 bash 版本 >= 3,您可以改为使用 bash 的内置正则表达式并执行以下操作:

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

这确实可以正确处理目录或 PATH 中有空格的情况。关于 bash 内置的正则表达式引擎是否足够慢,这可能比您的版本所做的字符串连接和插值效率低,但不知何故,它对我来说感觉更美观。

  • @ChristopherSmith - 回复:`不必要的引用`意味着你提前知道`$PATH`不为空。`"$PATH"` 确定 PATH 是否为空。同样,如果 `$1` 包含可能会混淆命令解析器的字符。将正则表达式放在引号 `"(^|:)$1(:|$)"` 中可以防止这种情况。 (2认同)

小智 9

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

当您需要 $HOME/bin 在 $PATH 的开头只出现一次而没有其他地方时,请不要接受替代品。


ter*_*don 7

这是我的(我相信它是几年前由我旧实验室的系统管理员 Oscar 编写的,这一切都归功于他),它在我的 bashrc 中已经存在多年了。它具有允许您根据需要预先或附加新目录的额外好处:

pathmunge () {
        if ! echo $PATH | /bin/egrep -q "(^|:)$1($|:)" ; then
           if [ "$2" = "after" ] ; then
              PATH=$PATH:$1
           else
              PATH=$1:$PATH
           fi
        fi
}
Run Code Online (Sandbox Code Playgroud)

用法:

$ 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/
Run Code Online (Sandbox Code Playgroud)


Rob*_*gue 7

这是一个替代解决方案,它具有删除冗余整体的额外优势:

function pathadd {
    PATH=:$PATH
    PATH=$1${PATH//:$1/}
}
Run Code Online (Sandbox Code Playgroud)

此函数的单个参数被添加到 PATH 之前,并且同一字符串的第一个实例将从现有路径中删除。换句话说,如果路径中已经存在目录,则将其提升到前面而不是添加为副本。

该函数的工作方式是在路径前添加一个冒号以确保所有条目的前面都有一个冒号,然后在删除该条目的情况下将新条目添加到现有路径中。最后一部分是使用 bash 的${var//pattern/sub}表示法进行的;有关更多详细信息,请参阅bash 手册

  • 缺陷很容易修复,如下:第二行需要用 `:` 替换整个路径,如下所示:`PATH=$1${PATH//:$1:/:}` (3认同)

小智 6

令我有点惊讶的是,还没有人提到这一点,\n但您可以使用readlink -f将相对路径转换为绝对路径,\n并将它们添加到 PATH 中。

\n\n

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

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

变成

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

1. 基础知识 \xe2\x80\x94 这有什么好处?

\n\n

readlink -f命令将(除其他外)\n将相对路径转换为绝对路径。\xc2\xa0\n这允许您执行类似的操作

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

2. 为什么我们要测试两次是否在 PATH 中?

\n\n

好吧,考虑上面的例子。\xc2\xa0\n如果用户第二次从目录中说出\n ,\n将是.\xc2\xa0\n当然,不会出现在.\xc2\xa0\n但是然后将被设置为\n(相当于 的绝对路径),它已经在.\xc2\xa0\n 中,所以我们需要避免第二次添加。pathappend\xc2\xa0./path/to/my/bin/dirARG..PATHARGA/path/to/my/bin/dir.PATH/path/to/my/bin/dirPATH

\n\n

也许更重要的readlink是,\n顾名思义,其主要目的是\n查看符号链接并读出它包含的路径名\n(即指向)。\xc2\xa0\n例如:

\n\n
$ ls -ld /usr/lib/perl/5.14\n-rwxrwxrwx  1   root   root    Sep  3  2015 /usr/lib/perl/5.14 -> 5.14.2\n$ readlink /usr/lib/perl/5.14\n5.14.2\n$ readlink -f /usr/lib/perl/5.14\n/usr/lib/perl/5.14.2\n
Run Code Online (Sandbox Code Playgroud)\n\n

现在,如果你说pathappend /usr/lib/perl/5.14,\n并且你已经/usr/lib/perl/5.14在你的 PATH 中,\n那么,\xe2\x80\x99s 很好;\xc2\xa0\n但是,如果/usr/lib/perl/5.14\xe2\x80\x99t 已经在您的 PATH 中,\n我们调用readlink并获取ARGA= /usr/lib/perl/5.14.2,\n然后我们将添加到PATH.\xc2\xa0\n但是等一下 \xe2\x80\x94 如果你已经说过pathappend /usr/lib/perl/5.14,\n那么你已经/usr/lib/perl/5.14.2在你的 PATH 中了,并且,\n我们需要再次检查它以避免PATH再次添加它。

\n\n

3. 处理什么\xe2\x80\x99s if ARGA=$(readlink -f "$ARG")

\n\n

如果\xe2\x80\x99不清楚,这一行测试是否成功readlink。\xc2\xa0\n这只是很好的防御性编程实践。\xc2\xa0\n如果我们\xe2\x80\x99要使用输出从 command\xc2\xa0 m \nas part\xc2\xa0of command\xc2\xa0 n(其中m \xc2\xa0<\xc2\xa0 n),\nit\xe2\x80\x99s 谨慎检查\n是否命令\ xc2\xa0 m失败并以某种方式处理。\xc2\xa0\nI\xc2\xa0don\xe2\x80\x99t 认为 \xe2\x80\x99s 可能会readlink失败 \xe2\x80\x94 但是,正如所讨论的\nin如何从 OS\xe2\x80\xafX \n 和其他地方检索任意文件的绝对路径,readlink是 GNU 的发明。\xc2\xa0\n它\xe2\x80\x99s 没有在 POSIX 中指定,因此它的可用性在Mac\xe2\x80\x8aOS、Solaris、\n和其他非 Linux Unix 是有问题的。\xc2\xa0\n(在 \xc2\xa0fact 中,我\xc2\xa0just read a \xc2\xa0comment that said\n\xe2 \x80\x9creadlink\xc2\xa0-f似乎不适用于\xc2\xa0Mac\xc2\xa0OS X\xc2\xa010.11.6,\n但realpath可以开箱即用。\xe2\x80\x9d\xc2\xa0\n所以,\xc2\ xa0如果您\xe2\x80\x99所在的系统没有\xe2\x80\x99 readlinkreadlink\xc2\xa0-f\xe2\x80\x99不工作,\n您\xc2\xa0可能\xc2\xa0能够\xc2 \xa0将此脚本修改为\xc2\xa0use realpath。)\xc2\xa0\n通过安装安全网,我们使代码更加可移植。

\n\n

当然,如果您\xe2\x80\x99 所在的系统没有\xe2\x80\x99 readlink(或\xc2\xa0 realpath),\n您\xe2\x80\x99 就不会想要执行。pathappend\xc2\xa0.

\n\n

第二个-d测试([ -d "$ARGA" ])确实可能是不必要的。\xc2\xa0\n我\xc2\xa0can\xe2\x80\x99t 想到任何场景\n其中$ARG是一个目录并且readlink成功,\n但是\xc2\xa0$ARGA不是一个目录。\ xc2\xa0\nI\xc2\xa0 只是复制并粘贴了第一个if语句来创建第三个语句,\nand\xc2\xa0I\xc2\xa0-d出于懒惰而将 \xc2\xa0 测试留在那里。

\n\n

4. 还有其他意见吗?

\n\n

是的。\xc2\xa0\n与这里的许多其他答案一样,\n这个测试每个参数是否是一个目录,\n如果是,则处理它,如果不是,则忽略它。\xc2\xa0\n这可能(或如果您 \xe2\x80\x99repathappend仅在 \xe2\x80\x9c .\xe2\x80\x9d 文件(如.bash_profile和)\n 和其他脚本中使用\xe2\x80\x99\n,则可能不够。.bashrc\xc2\xa0\n但是,正如这个答案所示(上图),\nit\xe2\x80\x99s 完全可以交互使用它。\xc2\xa0\n如果你这样做你会很困惑

\n\n
$ pathappend /usr/local/nysql/bin\n$ mysql\n-bash: mysql: command not found\n
Run Code Online (Sandbox Code Playgroud)\n\n
\n

您是否注意到我nysql在命令中说的是\pathappend而不是mysql?\ xc2 \ xa0 \ n并且pathappend\ xe2 \ x80 \ x99没有说什么;\ nit只是默默地忽略了不正确的参数?

\n
\n\n

正如我上面所说,\xe2\x80\x99s 是处理错误的好习惯。\xc2\xa0\n这里\xe2\x80\x99s 是一个例子:

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


小智 5

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

alias checkInPath="echo $PATH | tr ':' '\n' | grep -x -c "
Run Code Online (Sandbox Code Playgroud)

它所做的就是分割 : 字符上的路径,并将每个组件与您传入的参数进行比较。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
Run Code Online (Sandbox Code Playgroud)

将 echo 命令替换为 addToPath 或一些类似的别名/函数。


小智 5

对于前置,我喜欢@Russell 的解决方案,但有一个小错误:如果您尝试将诸如“/bin”之类的内容添加到“/sbin:/usr/bin:/var/usr/bin:/usr/local”的路径中/bin:/usr/sbin" 它替换了 "/bin:" 3 次(当它根本不匹配时)。将针对该问题的修复与来自 @gordon-davisson 的附加解决方案相结合,我得到了这个:

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


hyp*_*all 5

这个问题已经很饱和了,但到目前为止我还没有看到一种适用于任何类似 PATH 变量的通用方法

\n

例如:

\n
# prepend to PATH\n_path_prepend "$XDG_DATA_HOME/phpenv/bin"\n\n# prepend to MANPATH\n_path_prepend MANPATH "$XDG_DATA_HOME/shell-installer/man"\n\n# prepend to PERL5LIB\n_path_prepend PERL5LIB "$PERL_LOCAL_LIB_ROOT/lib/perl5"\n
Run Code Online (Sandbox Code Playgroud)\n

特征

\n
    \n
  • 符合 POSIX 标准
  • \n
  • 不使用 Bashdeclare -n
  • \n
\n
_path_prepend() {\n    if [ -n "$2" ]; then\n        case ":$(eval "echo \\$$1"):" in\n            *":$2:"*) :;;\n            *) eval "export $1=$2\\${$1:+\\":\\$$1\\"}" ;;\n        esac\n    else\n        case ":$PATH:" in\n            *":$1:"*) :;;\n            *) export PATH="$1${PATH:+":$PATH"}" ;;\n        esac\n    fi\n}\n\n_path_append() {\n    if [ -n "$2" ]; then\n        case ":$(eval "echo \\$$1"):" in\n            *":$2:"*) :;;\n            *) eval "export $1=\\${$1:+\\"\\$$1:\\"}$2" ;;\n        esac\n    else\n        case ":$PATH:" in\n            *":$1:"*) :;;\n            *) export PATH="${PATH:+"$PATH:"}$1" ;;\n        esac\n    fi\n}\n
Run Code Online (Sandbox Code Playgroud)\n

在这两个函数中,如果没有第二个参数,它将回退到添加硬编码的 PATH 变量

\n

:+部分的行为如戈登·戴维森在最佳答案${PATH:+"$PATH:"}的回复中所述

\n
\n

... ${variable:+value} 表示检查变量是否已定义且有非空值,如果有则给出 value 的求值结果。基本上,如果 PATH 开头非空,则将其设置为“$PATH:$1”;如果它是空白的,则将其设置为“$1”(注意缺少冒号)

\n
\n

用法

\n
\xe2\x9e\xa4 alfa=\n\xe2\x9e\xa4 _path_prepend alfa one; echo "$alfa"\none\n\xe2\x9e\xa4 _path_prepend alfa two; echo "$alfa"\ntwo:one\n\xe2\x9e\xa4 _path_prepend alfa three; echo "$alfa"\nthree:two:one\n\n\xe2\x9e\xa4 bravo=\n\xe2\x9e\xa4 _path_append bravo one; echo "$bravo"\none\n\xe2\x9e\xa4 _path_append bravo two; echo "$bravo"\none:two\n\xe2\x9e\xa4 _path_append bravo three; echo "$bravo"\none:two:three\n
Run Code Online (Sandbox Code Playgroud)\n