Qual é a maneira mais elegante de remover um caminho da variável $ PATH no Bash?

114

Ou, de forma mais geral, como removo um item de uma lista separada por dois pontos em uma variável de ambiente Bash?

Eu pensei ter visto uma maneira simples de fazer isso anos atrás, usando as formas mais avançadas de expansão de variável Bash, mas se foi, perdi o controle. Uma busca rápida no Google encontrou surpreendentemente poucos resultados relevantes e nenhum que eu chamaria de "simples" ou "elegante". Por exemplo, dois métodos usando sed e awk, respectivamente:

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)

Não existe nada simples? Existe algo análogo a uma função split () no Bash?

Atualizar:
Parece que preciso me desculpar por minha pergunta intencionalmente vaga; Eu estava menos interessado em resolver um caso de uso específico do que em provocar uma boa discussão. Felizmente, consegui!

Existem algumas técnicas muito inteligentes aqui. No final, adicionei as três funções a seguir à minha caixa de ferramentas. A mágica acontece em path_remove, que é amplamente baseado no uso inteligente de Martin York da awkvariável 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/:$//'`; }

O único problema real é o uso de sedpara remover os dois pontos à direita. Considerando o quão simples é o resto da solução de Martin, estou bastante disposto a viver com isso!


Questão relacionada: Como posso manipular elementos $ PATH em scripts de shell?

Ben Blank
fonte
Para qualquer variável: WORK=`echo -n ${1} | awk -v RS=: -v ORS=: '$0 != "'${3}'"' | sed 's/:$//'`; eval "export ${2}=${WORK}"mas você deve chamá-la como func $VAR VAR pattern(com base em @ martin-york e @andrew-aylett)
vesperto

Respostas:

51

Um minuto com awk:

# Strip all paths with SDE in them.
#
export PATH=`echo ${PATH} | awk -v RS=: -v ORS=: '/SDE/ {next} {print}'`

Editar: É resposta aos comentários abaixo:

$ export a="/a/b/c/d/e:/a/b/c/d/g/k/i:/a/b/c/d/f:/a/b/c/g:/a/b/c/d/g/i"
$ echo ${a}
/a/b/c/d/e:/a/b/c/d/f:/a/b/c/g:/a/b/c/d/g/i

## Remove multiple (any directory with a: all of them)
$ echo ${a} | awk -v RS=: -v ORS=: '/a/ {next} {print}'
## Works fine all removed

## Remove multiple including last two: (any directory with g)
$ echo ${a} | awk -v RS=: -v ORS=: '/g/ {next} {print}'
/a/b/c/d/e:/a/b/c/d/f:
## Works fine: Again!

Editar em resposta ao problema de segurança: (isso não é relevante para a pergunta)

export PATH=$(echo ${PATH} | awk -v RS=: -v ORS=: '/SDE/ {next} {print}' | sed 's/:*$//')

Isso remove quaisquer dois pontos finais deixados, excluindo as últimas entradas, o que adicionaria efetivamente .ao seu caminho.

Martin York
fonte
1
Falha ao tentar remover o último elemento ou vários elementos: no primeiro caso, adiciona o dir atual (como a string vazia; uma falha de segurança potencial), no segundo caso adiciona `` como um elemento de caminho.
Fred Foo
1
@larsmans: Funciona bem para mim. Observação: Vazio não é igual ao diretório atual, que é "./"
Martin York
2
Uma cadeia vazia como um "membro" da PATHvariável faz , como uma regra especial, denotam diretório atual em todas as conchas Unix desde pelo menos V7 Unix de 1979. Ele ainda faz em bash. Verifique o manual ou experimente você mesmo.
Fred Foo
1
@Martin: POSIX não exige esse comportamento, mas documenta e permite: pubs.opengroup.org/onlinepubs/9699919799/basedefs/…
Fred Foo
1
Há um problema ainda mais sutil ao remover o último elemento com isso: awk parece adicionar um byte nulo ao final da string . O que significa que se você anexar outro diretório ao PATH posteriormente, ele não será pesquisado.
sschuberth
55

Meu hack sujo:

echo ${PATH} > t1
vi t1
export PATH=$(cat t1)
Martin York
fonte
18
Nunca é um bom sinal quando a solução mais óbvia é a de -Automatize o processo. :-D
Ben Blank
Muito mais seguro do que as alternativas "elegantes".
Jortstek
44

Já que o grande problema com a substituição são os casos finais, que tal fazer com que os casos finais não sejam diferentes dos outros casos? Se o caminho já tivesse dois pontos no início e no final, poderíamos simplesmente pesquisar nossa string desejada envolta em dois pontos. Do jeito que está, podemos facilmente adicionar esses dois pontos e removê-los depois.

# PATH => /bin:/opt/a dir/bin:/sbin
WORK=:$PATH:
# WORK => :/bin:/opt/a dir/bin:/sbin:
REMOVE='/opt/a dir/bin'
WORK=${WORK/:$REMOVE:/:}
# WORK => :/bin:/sbin:
WORK=${WORK%:}
WORK=${WORK#:}
PATH=$WORK
# PATH => /bin:/sbin

Bash puro :).

Andrew Aylett
fonte
2
Eu adicionaria esta seção do tutorial para um pouco de cobertura extra: tldp.org/LDP/abs/html/string-manipulation.html
Cyber ​​Oliveira,
1
Usei isso porque parecia a solução mais simples. Foi super rápido e fácil, e você pode facilmente verificar seu trabalho com echo $ WORK logo antes da última linha onde você realmente altera a variável PATH.
Phil Gran
2
Absolutamente uma pequena joia. Exatamente o que eu estava tentando fazer quando encontrei este post. -Obrigado Andrew! BTW: Talvez você queira adicionar aspas em torno de ": $ PATH:", apenas no caso de conter espaços (o mesmo sobre "/ usr / bin") e a última linha "$ WORK".
2
Obrigado, @ PacMan-- :). Tenho certeza (apenas tentei) de que você não precisa de espaços para as atribuições WORKe PATHcomo a expansão da variável acontece depois que a linha é analisada em seções para atribuição de variável e execução de comando. REMOVEpode precisar ser citado, ou você pode simplesmente colocar sua string diretamente na substituição, se for uma constante.
Andrew Aylett
Eu sempre ficava confuso sobre quando as strings eram preservadas, então comecei sempre a fazer aspas duplas das strings. Obrigado novamente por esclarecer isso. :)
26

Esta é a solução mais simples que posso conceber:

#!/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[*]}"

O exemplo acima removerá qualquer elemento em $ PATH que contenha "usr". Você pode substituir "* usr *" por "/ home / user / bin" para remover apenas esse elemento.

atualizar por sschuberth

Mesmo que eu ache que os espaços em um $PATHsão uma ideia horrível , aqui está uma solução que dá conta disso:

PATH=$(IFS=':';t=($PATH);n=${#t[*]};a=();for ((i=0;i<n;i++)); do p="${t[i]%%*usr*}"; [ "${p}" ] && a[i]="${p}"; done;echo "${a[*]}");

ou

IFS=':'
t=($PATH)
n=${#t[*]}
a=()
for ((i=0;i<n;i++)); do
  p="${t[i]%%*usr*}"
  [ "${p}" ] && a[i]="${p}"
done
echo "${a[*]}"
melhorobô
fonte
2
Como um único liner: PATH = $ (IFS = ':'; t = ($ PATH); unset IFS; t = ($ {t [@] %% * usr *}); IFS = ':'; echo "$ {t [*]} ");
nicerobot
1
Esta solução não funciona com caminhos em PATH que contêm espaços; ele os substitui por dois pontos.
sschuberth
11

Aqui está um one-liner que, apesar das respostas aceitas e de maior pontuação , não adiciona caracteres invisíveis ao PATH e pode lidar com caminhos que contêm espaços:

export PATH=$(p=$(echo $PATH | tr ":" "\n" | grep -v "/cygwin/" | tr "\n" ":"); echo ${p%:})

Pessoalmente, também acho isso fácil de ler / entender e envolve apenas comandos comuns em vez de usar o awk.

sschuberth
fonte
2
... e se você quiser algo que possa lidar até mesmo com novas linhas em nomes de arquivos, você pode usar isto: export PATH=$(p=$(echo $PATH | tr ":" "\0" | grep -v -z "/cygwin/" | tr "\0" ":"); echo ${p%:}) (embora sem dúvida, você pode querer se perguntar por que precisa disso, se precisar :))
ehdr
Isso removerá correspondências parciais, o que provavelmente não é o que você deseja; Eu usaria grep -v "^/path/to/remove\$"ougrep -v -x "/path/to/remove"
ShadSterling
Boa solução, mas você realmente acha que tré mais comum do que awk? ;)
K.-Michael Aye
1
Absolutamente. Ambientes leves, como Git Bash no Windows, vêm com uma ferramenta simples, em trvez de um interpretador awk.
sschuberth
1
@ehdr: Você precisa substituir echo "..."por printf "%s" "..."para que funcione em caminhos semelhantes -ee semelhantes. Consulte stackoverflow.com/a/49418406/102441
Eric
8

Aqui está uma solução que:

  • é puro Bash,
  • não invoca outros processos (como 'sed' ou 'awk'),
  • não muda IFS,
  • não bifurca um sub-shell,
  • lida com caminhos com espaços, e
  • remove todas as ocorrências do argumento em PATH.

    removeFromPath () {
       pd local
       p = ": $ 1:"
       d = ": $ PATH:"
       d = $ {d // $ p /:}
       d = $ {d / #: /}
       CAMINHO = $ {d /%: /}
    }
robinbb
fonte
4
Eu gosto dessa solução. Talvez tornar os nomes das variáveis ​​mais descritivos?
Anukool
muito agradável. No entanto, não funciona para segmentos de caminho que contenham um asterisco (*). Às vezes, eles chegam lá acidentalmente.
Jörg de
6

function __path_remove () {
local D = ": $ {PATH}:";
["$ {D /: $ 1: /:}"! = "$ D"] && PATH = "$ {D /: $ 1: /:}";
PATH = "$ {PATH / #: /}";
exportar PATH = "$ {PATH /%: /}";
}

Desenterrei do meu arquivo .bashrc. Quando você brinca com o PATH e ele se perde, awk / sed / grep fica indisponível :-)

GreenFox
fonte
1
Esse é um ponto muito bom. (Nunca gostei de executar utilitários externos para coisas simples como esta).
6

A melhor opção de bash puro que encontrei até agora é a seguinte:

function path_remove {
  PATH=${PATH/":$1"/} # delete any instances in the middle or at the end
  PATH=${PATH/"$1:"/} # delete any instances at the beginning
}

Isso se baseia na resposta incorreta de Adicionar diretório a $ PATH se ainda não estiver lá no Superusuário.

Mark Booth
fonte
Isso também é muito bom. Eu testei. Se houver um caminho duplicado (por exemplo, dois que são exatamente iguais) em PATH, apenas um deles será removido. Você também pode transformá-lo em uma linha:removePath () { PATH=${PATH/":$1"/}; PATH=${PATH/"$1:"/}; }
Esta solução falha quando $PATHcontém uma subpasta do caminho de destino (ou seja, a ser excluído). Por exemplo: a:abc/def/bin:b-> a/bin:b, quando abc/defdeve ser excluído.
Robin Hsu
5

Acabei de usar as funções da distribuição bash, que aparentemente existem desde 1991. Ainda estão no pacote bash-docs no Fedora e costumavam ser usadas /etc/profile, mas não mais ...

$ rpm -ql bash-doc |grep pathfunc
/usr/share/doc/bash-4.2.20/examples/functions/pathfuncs
$ cat $(!!)
cat $(rpm -ql bash-doc |grep pathfunc)
#From: "Simon J. Gerraty" <[email protected]>
#Message-Id: <[email protected]>
#Subject: Re: a shell idea?
#Date: Mon, 09 Oct 1995 21:30:20 +1000


# NAME:
#       add_path.sh - add dir to path
#
# DESCRIPTION:
#       These functions originated in /etc/profile and ksh.kshrc, but
#       are more useful in a separate file.
#
# SEE ALSO:
#       /etc/profile
#
# AUTHOR:
#       Simon J. Gerraty <[email protected]>

#       @(#)Copyright (c) 1991 Simon J. Gerraty
#
#       This file is provided in the hope that it will
#       be of use.  There is absolutely NO WARRANTY.
#       Permission to copy, redistribute or otherwise
#       use this file is hereby granted provided that
#       the above copyright notice and this notice are
#       left intact.

# is $1 missing from $2 (or PATH) ?
no_path() {
        eval "case :\$${2-PATH}: in *:$1:*) return 1;; *) return 0;; esac"
}
# if $1 exists and is not in path, append it
add_path () {
  [ -d ${1:-.} ] && no_path $* && eval ${2:-PATH}="\$${2:-PATH}:$1"
}
# if $1 exists and is not in path, prepend it
pre_path () {
  [ -d ${1:-.} ] && no_path $* && eval ${2:-PATH}="$1:\$${2:-PATH}"
}
# if $1 is in path, remove it
del_path () {
  no_path $* || eval ${2:-PATH}=`eval echo :'$'${2:-PATH}: |
    sed -e "s;:$1:;:;g" -e "s;^:;;" -e "s;:\$;;"`
}
Sr. maluco
fonte
4

Eu escrevi uma resposta para isso aqui (usando awk também). Mas não tenho certeza de que é isso que você está procurando. Parece pelo menos claro para mim o que faz, em vez de tentar encaixar em uma linha. Para um liner simples, porém, que só remove coisas, eu recomendo

echo $PATH | tr ':' '\n' | awk '$0 != "/bin"' | paste -sd:

Substituir é

echo $PATH | tr ':' '\n' | 
    awk '$0 != "/bin"; $0 == "/bin" { print "/bar" }' | paste -sd:

ou (mais curto, mas menos legível)

echo $PATH | tr ':' '\n' | awk '$0 == "/bin" { print "/bar"; next } 1' | paste -sd:

De qualquer forma, para a mesma pergunta, e muitas respostas úteis, veja aqui .

Johannes Schaub - litb
fonte
E se você quiser remover linhas que contenham uma string parcial, use awk '$0 !~ "/bin"'. Ou seja, mantém as linhas que não contêm '/ bin' com o operador awk !~.
thoni56
3

Bem, no bash, como ele suporta expressão regular, eu simplesmente faria:

PATH=${PATH/:\/home\/user\/bin/}
esteira
fonte
Não é apenas a expansão do nome do caminho, não as expressões regulares?
dreamlax
2
Embora o bash suporte expressões regulares (a partir do bash 3), este não é um exemplo disso, é uma substituição de variável.
Robert Gamble
4
Esta é a expantion da variável padrão e a solução tem vários problemas. 1) não corresponderá ao primeiro elemento. 2) corresponderá a qualquer coisa que comece com "/ home / user / bin", não apenas "/ home / user / bin". 3) requer caracteres especiais de escape. Na melhor das hipóteses, eu diria que este é um exemplo incompleto.
nicerobot
2

Eu gosto das três funções mostradas na atualização do @BenBlank para sua pergunta original. Para generalizá-los, eu uso uma forma de 2 argumentos, que me permite definir PATH ou qualquer outra variável de ambiente que eu quiser:

path_append ()  { path_remove $1 $2; export $1="${!1}:$2"; }
path_prepend () { path_remove $1 $2; export $1="$2:${!1}"; }
path_remove ()  { export $1="`echo -n ${!1} | awk -v RS=: -v ORS=: '$1 != "'$2'"' | sed 's/:$//'`"; }

Exemplos de uso:

path_prepend PATH /usr/local/bin
path_append PERL5LIB "$DEVELOPMENT_HOME/p5/src/perlmods"

Observe que também adicionei algumas aspas para permitir o processamento adequado de nomes de caminhos que contenham espaços.

Cary Millsap
fonte
2

Qual é a maneira mais elegante de remover um caminho da variável $ PATH no Bash?

O que é mais elegante do que awk?

path_remove ()  { export PATH=`echo -n $PATH | awk -v RS=: -v ORS=: '$0 != "'$1'"' | sed 's/:$//'`; 

Pitão! É uma solução mais legível e sustentável e é fácil inspecionar para ver se ela está realmente fazendo o que você deseja.

Digamos que você queira remover o primeiro elemento do caminho?

PATH="$(echo "$PATH" | python -c "import sys; path = sys.stdin.read().split(':'); del path[0]; print(':'.join(path))")"

(Em vez de canalizar de echo, os.getenv['PATH']seria um pouco mais curto e forneceria o mesmo resultado que o acima, mas estou preocupado que Python possa fazer algo com essa variável de ambiente, então é provavelmente melhor canalizá-la diretamente do ambiente que você considera .)

Da mesma forma para remover do final:

PATH="$(echo "$PATH" | python -c "import sys; path = sys.stdin.read().split(':'); del path[-1]; print(':'.join(path))")"

Para tornar essas funções de shell reutilizáveis ​​que você pode, por exemplo, manter em seu arquivo .bashrc:

strip_path_first () {
    PATH="$(echo "$PATH" | 
    python -c "import sys; path = sys.stdin.read().split(':'); del path[0]; print(':'.join(path))")"
}

strip_path_last () {
    PATH="$(echo "$PATH" | 
    python -c "import sys; path = sys.stdin.read().split(':'); del path[-1]; print(':'.join(path))")"
}
Aaron Hall
fonte
1

Sim, colocar dois pontos no final de PATH, por exemplo, torna a remoção de um caminho um pouco menos desajeitada e sujeita a erros.

path_remove ()  { 
   declare i newPATH
   newPATH="${PATH}:"
   for ((i=1; i<=${#@}; i++ )); do
      #echo ${@:${i}:1}
      newPATH="${newPATH//${@:${i}:1}:/}" 
   done
   export PATH="${newPATH%:}" 
   return 0; 
} 

path_remove_all ()  {
   declare i newPATH
   shopt -s extglob
   newPATH="${PATH}:"
   for ((i=1; i<=${#@}; i++ )); do
      newPATH="${newPATH//+(${@:${i}:1})*([^:]):/}" 
      #newPATH="${newPATH//+(${@:${i}:1})*([^:])+(:)/}" 
   done
   shopt -u extglob 
   export PATH="${newPATH%:}" 
   return 0 
} 

path_remove /opt/local/bin /usr/local/bin

path_remove_all /opt/local /usr/local 
cirilo
fonte
1

Se você está preocupado em remover duplicatas em $ PATH, a maneira mais elegante, IMHO, seria não adicioná-las em primeiro lugar. Em 1 linha:

if ! $( echo "$PATH" | tr ":" "\n" | grep -qx "$folder" ) ; then PATH=$PATH:$folder ; fi

$ pasta pode ser substituída por qualquer coisa e pode conter espaços ("/ home / usuário / meus documentos")

MestreLion
fonte
1

A solução bash pura mais elegante que encontrei até hoje:

pathrm () {                                                                      
  local IFS=':'                                                                  
  local newpath                                                                  
  local dir                                                                      
  local pathvar=${2:-PATH}                                                       
  for dir in ${!pathvar} ; do                                                    
    if [ "$dir" != "$1" ] ; then                                                 
      newpath=${newpath:+$newpath:}$dir                                          
    fi                                                                           
  done                                                                           
  export $pathvar="$newpath"                                                        
}

pathprepend () {                                                                 
  pathrm $1 $2                                                                   
  local pathvar=${2:-PATH}                                                       
  export $pathvar="$1${!pathvar:+:${!pathvar}}"                                  
}

pathappend () {                                                                    
  pathrm $1 $2                                                                   
  local pathvar=${2:-PATH}                                                       
  export $pathvar="${!pathvar:+${!pathvar}:}$1"                                  
} 
TriangleTodd
fonte
1

A maioria das outras soluções sugeridas contar apenas com strings e não levam em conta os segmentos de caminho contendo nomes especiais como ., ..ou ~. A função bash abaixo resolve strings de diretório em seu argumento e em segmentos de caminho para encontrar correspondências lógicas de diretório, bem como correspondências de string.

rm_from_path() {
  pattern="${1}"
  dir=''
  [ -d "${pattern}" ] && dir="$(cd ${pattern} && pwd)"  # resolve to absolute path

  new_path=''
  IFS0=${IFS}
  IFS=':'
  for segment in ${PATH}; do
    if [[ ${segment} == ${pattern} ]]; then             # string match
      continue
    elif [[ -n ${dir} && -d ${segment} ]]; then
      segment="$(cd ${segment} && pwd)"                 # resolve to absolute path
      if [[ ${segment} == ${dir} ]]; then               # logical directory match
        continue
      fi
    fi
    new_path="${new_path}${IFS}${segment}"
  done
  new_path="${new_path/#${IFS}/}"                       # remove leading colon, if any
  IFS=${IFS0}

  export PATH=${new_path}
}

Teste:

$ mkdir -p ~/foo/bar/baz ~/foo/bar/bif ~/foo/boo/bang
$ PATH0=${PATH}
$ PATH=~/foo/bar/baz/.././../boo/././../bar:${PATH}  # add dir with special names
$ rm_from_path ~/foo/boo/../bar/.  # remove same dir with different special names
$ [ ${PATH} == ${PATH0} ] && echo 'PASS' || echo 'FAIL'
jwfearn
fonte
mais um para fora da caixa
Martin York
1

Linux from Scratch define três funções Bash em /etc/profile:

# Functions to help us manage paths.  Second argument is the name of the
# path variable to be modified (default: PATH)
pathremove () {
        local IFS=':'
        local NEWPATH
        local DIR
        local PATHVARIABLE=${2:-PATH}
        for DIR in ${!PATHVARIABLE} ; do
                if [ "$DIR" != "$1" ] ; then
                  NEWPATH=${NEWPATH:+$NEWPATH:}$DIR
                fi
        done
        export $PATHVARIABLE="$NEWPATH"
}

pathprepend () {
        pathremove $1 $2
        local PATHVARIABLE=${2:-PATH}
        export $PATHVARIABLE="$1${!PATHVARIABLE:+:${!PATHVARIABLE}}"
}

pathappend () {
        pathremove $1 $2
        local PATHVARIABLE=${2:-PATH}
        export $PATHVARIABLE="${!PATHVARIABLE:+${!PATHVARIABLE}:}$1"
}

export -f pathremove pathprepend pathappend

Ref: http://www.linuxfromscratch.org/blfs/view/svn/postlfs/profile.html

Kevinarpe
fonte
1

Eu sei que esta pergunta é sobre BASH, que todos deveriam preferir, mas como eu gosto de simetria e às vezes sou obrigado a usar "csh", construí o equivalente a "path_prepend ()", "path_append ()" e "path_remove () "solução elegante acima.

A essência é que "csh" não tem funções, então coloquei pequenos scripts de shell em meu diretório bin pessoal que agem como as funções. Eu crio aliases para SOURCE esses scripts para fazer as alterações de variáveis ​​de ambiente designadas.

~ / bin / _path_remove.csh:

set _resolve = `eval echo $2`
setenv $1 `eval echo -n \$$1 | awk -v RS=: -v ORS=: '$1 != "'${_resolve}'"' | sed 's/:$//'`;
unset _resolve

~ / bin / _path_append.csh:

source ~/bin/_path_remove.csh $1 $2
set _base = `eval echo \$$1`
set _resolve = `eval echo $2`
setenv $1 ${_base}:${_resolve}
unset _base _resolve

~ / bin / _path_prepend.csh:

source ~/bin/_path_remove.csh $1 $2
set _base = `eval echo \$$1`
set _resolve = `eval echo $2`
setenv $1 ${_resolve}:${_base}
unset _base _resolve

~ / bin / .cshrc:


alias path_remove  "source ~/bin/_path_remove.csh  '\!:1' '\!:2'"
alias path_append  "source ~/bin/_path_append.csh  '\!:1' '\!:2'"
alias path_prepend "source ~/bin/_path_prepend.csh '\!:1' '\!:2'"

Você pode usá-los assim ...

%(csh)> path_append MODULEPATH ${HOME}/modulefiles
Lance ET Compte
fonte
0

Uma vez que isso tende a ser bastante problemático, já que NÃO EXISTE uma maneira elegante, recomendo evitar o problema reorganizando a solução: construa seu PATH em vez de tentar derrubá-lo.

Eu poderia ser mais específico se soubesse o contexto do seu problema real. Nesse ínterim, usarei uma construção de software como contexto.

Um problema comum com compilações de software é que ele quebra em algumas máquinas, principalmente devido a como alguém configurou seu shell padrão (PATH e outras variáveis ​​de ambiente). A solução elegante é tornar seus scripts de construção imunes especificando totalmente o ambiente de shell. Codifique seus scripts de construção para definir o PATH e outras variáveis ​​de ambiente com base em peças de montagem que você controla, como a localização do compilador, bibliotecas, ferramentas, componentes, etc. Torne cada item configurável algo que você possa definir, verificar e em seguida, use apropriadamente em seu script.

Por exemplo, eu tenho um build Java baseado em Maven WebLogic que herdei do meu novo empregador. O script de construção é conhecido por ser frágil, e outro novo funcionário e eu passamos três semanas (não em tempo integral, apenas aqui e ali, mas ainda muitas horas) fazendo-o funcionar em nossas máquinas. Uma etapa essencial foi assumir o controle do PATH para saber exatamente qual Java, qual Maven e qual WebLogic estava sendo invocado. Eu criei variáveis ​​de ambiente para apontar para cada uma dessas ferramentas, então calculei o PATH com base nessas e em algumas outras. Técnicas semelhantes domesticaram as outras configurações configuráveis, até que finalmente criamos uma compilação reproduzível.

A propósito, não use o Maven, Java é bom, e só compre o WebLogic se você realmente precisar de seu clustering (mas de outra forma não, e especialmente de seus recursos proprietários).

Muitas felicidades.

Rob Williams
fonte
às vezes você não tem acesso root e seu administrador gerencia o seu PATH. Claro, você pode criar seu próprio, mas toda vez que seu administrador move algo, você precisa descobrir onde ele o colocou. Isso acaba com o propósito de ter um administrador.
Shep
0

Assim como com @litb, eu contribuí com uma resposta para a pergunta " Como faço para manipular elementos $ PATH em scripts de shell ", portanto, minha resposta principal está lá.

A funcionalidade de 'divisão' em bashe em outros derivados do Bourne shell é mais facilmente obtida com $IFSo separador entre campos. Por exemplo, para definir os argumentos posicionais ( $1, $2, ...) para os elementos da PATH, use:

set -- $(IFS=":"; echo "$PATH")

Funcionará bem desde que não haja espaços em $ PATH. Fazê-lo funcionar para elementos de caminho contendo espaços é um exercício não trivial - deixado para o leitor interessado. Provavelmente, é mais simples lidar com isso usando uma linguagem de script como Perl.

Eu também tenho um script, clnpathque uso extensivamente para definir meu PATH. Documentei na resposta a " Como evitar a duplicação da variável PATH no csh ".

Jonathan Leffler
fonte
IFS =: a = ($ PATH); IFS = divisão também é bom. funciona se contiverem espaços também. mas então você tem um array, e tem que mexer com loops for e outros para remover os nomes.
Johannes Schaub - litb
Sim; fica complicado - como com meu comentário atualizado, provavelmente é mais simples usar uma linguagem de script neste ponto.
Jonathan Leffler
0

O que torna esse problema irritante são os casos de postes de cerca entre o primeiro e o último elemento. O problema pode ser resolvido elegantemente alterando o IFS e usando um array, mas não sei como reintroduzir os dois pontos depois que o caminho é convertido para a forma de array.

Aqui está uma versão um pouco menos elegante que remove um diretório do $PATHuso apenas da manipulação de strings. Eu testei.

#!/bin/bash
#
#   remove_from_path dirname
#
#   removes $1 from user's $PATH

if [ $# -ne 1 ]; then
  echo "Usage: $0 pathname" 1>&2; exit 1;
fi

delendum="$1"
NEWPATH=
xxx="$IFS"
IFS=":"
for i in $PATH ; do
  IFS="$xxx"
  case "$i" in
    "$delendum") ;; # do nothing
    *) [ -z "$NEWPATH" ] && NEWPATH="$i" || NEWPATH="$NEWPATH:$i" ;;
  esac
done

PATH="$NEWPATH"
echo "$PATH"
Norman Ramsey
fonte
0

Aqui está um one-liner Perl:

PATH=`perl -e '$a=shift;$_=$ENV{PATH};s#:$a(:)|^$a:|:$a$#$1#;print' /home/usr/bin`

A $avariável obtém o caminho a ser removido. Os comandos s(substituto) e printoperam implicitamente na $_variável.

JA Faucett
fonte
0

Coisas boas aqui. Eu uso este para evitar adicionar ingênuos em primeiro lugar.

#!/bin/bash
#
######################################################################################
#
# Allows a list of additions to PATH with no dupes
# 
# Patch code below into your $HOME/.bashrc file or where it
# will be seen at login.
#
# Can also be made executable and run as-is.
#
######################################################################################

# add2path=($HOME/bin .)                  ## uncomment space separated list 
if [ $add2path ]; then                    ## skip if list empty or commented out
for nodup in ${add2path[*]}
do
    case $PATH in                 ## case block thanks to MIKE511
    $nodup:* | *:$nodup:* | *:$nodup ) ;;    ## if found, do nothing
    *) PATH=$PATH:$nodup          ## else, add it to end of PATH or
    esac                          ## *) PATH=$nodup:$PATH   prepend to front
done
export PATH
fi
## debug add2path
echo
echo " PATH == $PATH"
echo
Ongoto
fonte
1
Você pode simplificar sua instrução de caso adicionando dois-pontos à esquerda e à direita na string PATH:case ":$PATH:" in (*:"$nodup":*) ;; (*) PATH="$PATH:$nodup" ;; esac
glenn jackman
0

Com o globbing estendido ativado, é possível fazer o seguinte:

# delete all /opt/local paths in PATH
shopt -s extglob 
printf "%s\n" "${PATH}" | tr ':' '\n' | nl
printf "%s\n" "${PATH//+(\/opt\/local\/)+([^:])?(:)/}" | tr ':' '\n' | nl 

man bash | less -p extglob
carlo
fonte
0

One-liner de globbing estendido (bem, mais ou menos):

path_remove ()  { shopt -s extglob; PATH="${PATH//+(${1})+([^:])?(:)/}"; export PATH="${PATH%:}"; shopt -u extglob; return 0; } 

Parece não haver necessidade de escapar das barras em $ 1.

path_remove ()  { shopt -s extglob; declare escArg="${1//\//\\/}"; PATH="${PATH//+(${escArg})+([^:])?(:)/}"; export PATH="${PATH%:}"; shopt -u extglob; return 0; } 
carlo
fonte
0

Adicionando dois pontos ao PATH, também podemos fazer algo como:

path_remove ()  { 
   declare i newPATH
   # put a colon at the beginning & end AND double each colon in-between
   newPATH=":${PATH//:/::}:"   
   for ((i=1; i<=${#@}; i++)); do
       #echo ${@:${i}:1}
       newPATH="${newPATH//:${@:${i}:1}:/}"   # s/:\/fullpath://g
   done
   newPATH="${newPATH//::/:}"
   newPATH="${newPATH#:}"      # remove leading colon
   newPATH="${newPATH%:}"      # remove trailing colon
   unset PATH 
   PATH="${newPATH}" 
   export PATH
   return 0 
} 


path_remove_all ()  {
   declare i newPATH extglobVar
   extglobVar=0
   # enable extended globbing if necessary
   [[ ! $(shopt -q extglob) ]]  && { shopt -s extglob; extglobVar=1; }
   newPATH=":${PATH}:"
   for ((i=1; i<=${#@}; i++ )); do
      newPATH="${newPATH//:+(${@:${i}:1})*([^:])/}"     # s/:\/path[^:]*//g
   done
   newPATH="${newPATH#:}"      # remove leading colon
   newPATH="${newPATH%:}"      # remove trailing colon
   # disable extended globbing if it was enabled in this function
   [[ $extglobVar -eq 1 ]] && shopt -u extglob
   unset PATH 
   PATH="${newPATH}" 
   export PATH
   return 0 
} 

path_remove /opt/local/bin /usr/local/bin

path_remove_all /opt/local /usr/local 
proxxy
fonte
0

Em path_remove_all (por proxxy):

-newPATH="${newPATH//:+(${@:${i}:1})*([^:])/}" 
+newPATH="${newPATH//:${@:${i}:1}*([^:])/}"        # s/:\/path[^:]*//g 
marius
fonte
0

Embora este seja um tópico muito antigo, pensei que esta solução poderia ser do seu interesse:

PATH="/usr/lib/ccache:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games"
REMOVE="ccache" # whole or part of a path :)
export PATH=$(IFS=':';p=($PATH);unset IFS;p=(${p[@]%%$REMOVE});IFS=':';echo "${p[*]}";unset IFS)
echo $PATH # outputs /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games

encontrei nesta postagem do blog . Acho que gosto mais deste :)

mjc
fonte
0

Adotei uma abordagem um pouco diferente da maioria das pessoas aqui e me concentrei especificamente apenas na manipulação de cordas, assim:

path_remove () {
    if [[ ":$PATH:" == *":$1:"* ]]; then
        local dirs=":$PATH:"
        dirs=${dirs/:$1:/:}
        export PATH="$(__path_clean $dirs)"
    fi
}
__path_clean () {
    local dirs=${1%?}
    echo ${dirs#?}
}

A descrição acima é um exemplo simplificado das funções finais que uso. Eu também criei path_add_beforee path_add_afterpermitindo que você insira um caminho antes / depois de um caminho especificado já em PATH.

O conjunto completo de funções está disponível em path_helpers.sh em meus dotfiles . Eles suportam totalmente a remoção / acréscimo / prefixação / inserção no início / meio / fim da string PATH.

jimeh
fonte