Questão Adicione o diretório a $ PATH, se ainda não estiver lá


Alguém escreveu uma função bash para adicionar um diretório ao $ PATH somente se ele não estiver lá?

Eu normalmente adiciono ao PATH usando algo como:

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

Se eu construir meu PATH em .bash_profile, ele não será lido, a menos que a sessão em que estou seja uma sessão de login - o que nem sempre é verdade. Se eu construir meu PATH no .bashrc, ele será executado com cada subshell. Então, se eu iniciar uma janela do Terminal e, em seguida, executar a tela e, em seguida, executar um script de shell, recebo:

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

Vou tentar construir uma função bash chamada add_to_path() que adiciona apenas o diretório, se não estiver lá. Mas, se alguém já escreveu (ou descobriu) tal coisa, não vou gastar tempo com isso.


116


origem


Vejo stackoverflow.com/questions/273909/… para alguma infra-estrutura que possa ajudar. - dmckee
unix.stackexchange.com/questions/4965/… - Ciro Santilli 新疆改造中心 六四事件 法轮功
Se você enquadrar o problema como "apenas adicionando se já não estiver lá", ficará rudemente surpreso quando chegar o dia em que é importante para o item inserido estar no início, mas não termina aí. Uma abordagem melhor seria inserir o elemento e, em seguida, remover duplicatas, portanto, se a nova entrada já estiver lá, ela será efetivamente movida para o início. - Don Hatch


Respostas:


Do meu .bashrc:

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

Observe que o PATH já deve estar marcado como exportado, portanto, a reexportação não é necessária. Isso verifica se o diretório existe e é um diretório antes de adicioná-lo, com o qual você pode não se importar.

Além disso, isso adiciona o novo diretório ao final do caminho; colocar no começo, use PATH="$1${PATH:+":$PATH"}" em vez do acima PATH= linha.


116



Eu me importo. - Dennis Williamson
@ Neil: Funciona, porque se compara com ":$PATH:" em vez de apenas "$PATH" - Gordon Davisson
@GordonDavisson: Peço desculpas, meu teste estava errado e você está correto. - Neil
@GordonDavisson Qual é o ponto das coisas nas chaves. Eu não consigo resolver isso "${PATH:+"$PATH:"}$ 1 " - boatcoder
@ Mark0978: Foi o que fiz para consertar o problema que o bukzor apontou. ${variable:+value} significa verificar se variable é definido e tem um valor não vazio, e se ele fornece o resultado da avaliação value. Basicamente, se PATH não é em branco para começar, ele define "$PATH:$1"; se estiver em branco, ele define apenas "$1" (note a falta de dois pontos). - Gordon Davisson


Expandindo a resposta de Gordon Davisson, isso suporta vários argumentos

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

Então você pode fazer path path1 caminho path3 ...

Para prepending,

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

Semelhante ao pathappend, você pode fazer

pathprepend path1 path2 path3 ...


19



Isso é ótimo! Eu fiz uma pequena mudança. Para a função 'pathprepend', é conveniente ler os argumentos em sentido inverso, para que você possa dizer, por exemplo, pathprepend P1 P2 P3 e acabar com PATH=P1:P2:P3. Para obter esse comportamento, mude for ARG in "$@" do para for ((i=$#; i>0; i--)); do ARG=${!i} - ishmael
Obrigado @ishmael, boa sugestão, editei a resposta. Eu percebo que seu comentário tem mais de dois anos, mas eu não voltei desde então. Eu tenho que descobrir como obter e-mails de troca de pilha para pousar na minha caixa de entrada! - Guillaume Perrault-Archambault


Aqui está algo de minha resposta para essa questão combinado com a estrutura da função de Doug Harris. Ele usa expressões regulares Bash:

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

12



Isso funcionou para mim usando apenas $1 ao invés de ${1} - Andrei
@ Andrei: Sim, as chaves são desnecessárias neste caso. Não sei por que os incluí. - Dennis Williamson


Coloque isso nos comentários para a resposta selecionada, mas os comentários não parecem apoiar a formatação do PRE, portanto, adicione a resposta aqui:

@ gordon-davisson Eu não sou um grande fã de citações e concatenações desnecessárias. Assumindo que você está usando uma versão bash> = 3, você pode usar os regexs internos do bash e fazer:

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

Isso manipula corretamente casos em que há espaços no diretório ou no PATH. Há alguma dúvida sobre se o bash é construído no mecanismo regex é lento o suficiente para que isso possa ser menos eficiente do que a concatenação e interpolação de strings que sua versão faz, mas de alguma forma isso parece mais esteticamente limpo para mim.


10



Suporte de comentários formatting using the backtick apenas mas você não tem nenhum controle de parágrafo decente. - boatcoder
Isso coloca a adição no final. Muitas vezes é desejável adicionar ao início para substituir os locais existentes. - Dennis Williamson
@DennisWilliamson Esse é um ponto justo, embora eu não recomende isso como o comportamento padrão. Não é difícil descobrir como mudar para um pré-final. - Christopher Smith
@ChristopherSmith - re: unnecessary quoting implica que você sabe antes do tempo que $PATH não é nulo. "$PATH" torna OK se o PATH é nulo ou não. Da mesma forma se $1contém caracteres que podem confundir o analisador de comandos. Colocando o regex entre aspas "(^|:)$1(:|$)" impede isso. - Jesse Chisholm
@ JesseChisholm: Na verdade, acredito que o ponto de Christopher é que as regras são diferentes entre [[ e ]]. Eu prefiro citar tudo que possa precisar ser citado, a menos que isso cause falhas, mas acredito que ele esteja certo e que as citações não são realmente verdadeiras. necessário por aí $PATH. Por outro lado, parece-me que você está certo sobre $1. - Scott


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
}

Quando você precisar que $ HOME / bin apareça exatamente uma vez no início de seu $ PATH e em nenhum outro lugar, não aceite substitutos.


6



Obrigado, essa é uma boa solução elegante, mas descobri que tinha que fazer PATH=${PATH/"... ao invés de PATH=${PATH//"... para fazê-lo funcionar. - Mark Booth
O formulário de barra dupla deve corresponder a qualquer número de correspondências; a barra única corresponde apenas à primeira (procure por "Substituição de padrão" na página man bash). Não tenho certeza porque não funcionou ... - andybuckley
Isso falha no caso incomum que $1 é a única entrada (sem dois pontos). A entrada é duplicada. - Dennis Williamson
Também elimina de forma muito agressiva, conforme PeterS6g. - Dennis Williamson


Aqui está uma solução alternativa que tem a vantagem adicional de remover entradas redundantes:

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

O único argumento para essa função é prefixado no PATH e a primeira instância da mesma sequência é removida do caminho existente. Em outras palavras, se o diretório já existir no caminho, ele será promovido para a frente, em vez de ser adicionado como duplicado.

A função funciona ao prefixar dois pontos no caminho para garantir que todas as entradas tenham dois pontos na frente e, em seguida, prefixar a nova entrada no caminho existente com essa entrada removida. A última parte é executada usando o bash ${var//pattern/sub} notação; Vejo o manual de bash para mais detalhes.


6



Bom pensamento; implementação falha. Considere o que acontece se você já tem /home/robert na tua PATH e você pathadd /home/rob. - Scott


Aqui está o meu (eu acredito que foi escrito anos atrás por Oscar, o administrador do meu antigo laboratório, todo o crédito para ele), tem estado no meu bechrc há séculos. Ele tem o benefício adicional de permitir que você preencha ou anexe o novo diretório conforme desejado:

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

Uso:

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

5





Para prefixar, eu gosto da solução do @Russell, mas há um pequeno bug: se você tentar adicionar algo como "/ bin" em um caminho de "/ sbin: / usr / bin: / var / usr / bin: / usr / local / bin: / usr / sbin "substitui" / bin: "3 vezes (quando realmente não combinou). Combinando uma correção para isso com a solução anexa de @ gordon-davisson, eu recebo isto:

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
}

5





Um simples apelido como este abaixo deve fazer o truque:

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

Tudo o que ele faz é dividir o caminho no caractere: e comparar cada componente com o argumento que você passa. O grep verifica uma correspondência de linha completa e imprime a contagem.

Uso da amostra:

$ 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

Substitua o comando echo por addToPath ou algum alias / função similar.


4



Usar "grep -x" é provavelmente mais rápido que o loop que eu coloquei na minha função bash. - Doug Harris