Pergunta

Existe uma maneira idiomática de remoção de elementos de PATH-como shell variáveis?

Isso é que eu quero tomar

PATH=/home/joe/bin:/usr/local/bin:/usr/bin:/bin:/path/to/app/bin:.

e Remover ou substituir o /path/to/app/bin sem sobrepor o resto da variável. Pontos extras para permitir que eu put novos elementos em posições arbitrárias. O alvo será reconhecível por uma corda bem definido, e pode ocorrer em qualquer ponto na lista.

Eu sei que eu vi este feito, e algo provavelmente pode remendar em meu próprio, mas eu estou procurando uma abordagem agradável. Portabilidade e padronização um plus.

Eu uso bash, mas o exemplo é bem-vindo no seu shell favorito também.


O contexto aqui é um dos necessidade de mudar convenientemente entre várias versões (uma para fazer a análise, outro para trabalhar sobre o quadro) de um grande pacote de análise científica que produz uma dúzia de executáveis, tem escondido de dados em todo o sistema de arquivos, e ambiente usos variável para ajudar a encontrar todas essas coisas. Eu gostaria de escrever um script que seleciona uma versão, e necessidade de ser capaz de remover os elementos $PATH relativos à versão atualmente ativa e substituí-los com os mesmos elementos relacionados com a nova versão.


Isto está relacionado com o problema de impedir que elementos $PATH repetidas quando re-execução de scripts de login e similares.


Foi útil?

Solução

Dirigindo-se a solução proposta de dmckee:

  1. Embora algumas versões do Bash pode permitir hífens em nomes de função, outros (MacOS X) não.
  2. Não vejo a necessidade de utilização de retorno imediatamente antes do fim da função.
  3. Não vejo a necessidade de todos os pontos e vírgulas.
  4. Não vejo por que você tem caminho-element-by-padrão de exportar um valor. Pense export como equivalente a definir (ou mesmo criando) uma variável global -. Algo a ser evitado sempre que possível
  5. Eu não tenho certeza do que você espera 'replace-path PATH $PATH /usr' para fazer, mas não faz o que eu poderia esperar.

Considere um valor PATH que começa contendo:

.
/Users/jleffler/bin
/usr/local/postgresql/bin
/usr/local/mysql/bin
/Users/jleffler/perl/v5.10.0/bin
/usr/local/bin
/usr/bin
/bin
/sw/bin
/usr/sbin
/sbin

O resultado eu tenho (de 'replace-path PATH $PATH /usr') é:

.
/Users/jleffler/bin
/local/postgresql/bin
/local/mysql/bin
/Users/jleffler/perl/v5.10.0/bin
/local/bin
/bin
/bin
/sw/bin
/sbin
/sbin

Eu teria esperado para conseguir minhas costas caminho original desde / usr não aparece como um (completa) elemento do caminho, apenas como parte de um elemento do caminho.

Isto pode ser fixo no replace-path modificando um dos comandos sed:

export $path=$(echo -n $list | tr ":" "\n" | sed "s:^$removestr\$:$replacestr:" |
               tr "\n" ":" | sed "s|::|:|g")

Eu usei ':' em vez de '|' para separar partes do substituto desde '|' pode (em teoria) aparecem em um componente do caminho, ao passo que, por definição, de PATH, dois pontos não pode. Eu observo que a segunda sed poderia eliminar o diretório atual a partir do meio de um caminho. Ou seja, um valor legítimo (embora perversa) da PATH poderia ser:

PATH=/bin::/usr/local/bin

Após o processamento, o diretório atual não seria mais no caminho.

Uma mudança semelhante a âncora do jogo é apropriado em path-element-by-pattern:

export $target=$(echo -n $list | tr ":" "\n" | grep -m 1 "^$pat\$")

noto de passagem que grep -m 1 não é padrão (é uma extensão GNU, também disponível no MacOS X). E, de fato, the-n opção para echo também é não-padrão; você seria melhor simplesmente apagar o cólon de fuga que é adicionado em virtude de converter a nova linha de eco em dois pontos. Desde caminho-de elemento por padrão é utilizado apenas uma vez, tem efeitos secundários indesejáveis ??(isto clobbers qualquer pré-existente exportados variável chamada $removestr), que pode ser substituída de forma sensata por sua corpo. Isto, junto com o uso mais liberal de citações para evitar problemas com espaços ou expansão de nome de arquivo indesejado, leva a:

# path_tools.bash
#
# A set of tools for manipulating ":" separated lists like the
# canonical $PATH variable.
#
# /bin/sh compatibility can probably be regained by replacing $( )
# style command expansion with ` ` style
###############################################################################
# Usage:
#
# To remove a path:
#    replace_path         PATH $PATH /exact/path/to/remove
#    replace_path_pattern PATH $PATH <grep pattern for target path>
#
# To replace a path:
#    replace_path         PATH $PATH /exact/path/to/remove /replacement/path
#    replace_path_pattern PATH $PATH <target pattern> /replacement/path
#
###############################################################################

# Remove or replace an element of $1
#
#   $1 name of the shell variable to set (e.g. PATH)
#   $2 a ":" delimited list to work from (e.g. $PATH)
#   $3 the precise string to be removed/replaced
#   $4 the replacement string (use "" for removal)
function replace_path () {
    path=$1
    list=$2
    remove=$3
    replace=$4        # Allowed to be empty or unset

    export $path=$(echo "$list" | tr ":" "\n" | sed "s:^$remove\$:$replace:" |
                   tr "\n" ":" | sed 's|:$||')
}

# Remove or replace an element of $1
#
#   $1 name of the shell variable to set (e.g. PATH)
#   $2 a ":" delimited list to work from (e.g. $PATH)
#   $3 a grep pattern identifying the element to be removed/replaced
#   $4 the replacement string (use "" for removal)
function replace_path_pattern () {
    path=$1
    list=$2
    removepat=$3
    replacestr=$4        # Allowed to be empty or unset

    removestr=$(echo "$list" | tr ":" "\n" | grep -m 1 "^$removepat\$")
    replace_path "$path" "$list" "$removestr" "$replacestr"
}

Eu tenho um script Perl chamado echopath que considero útil ao depurar problemas com PATH-like variáveis:

#!/usr/bin/perl -w
#
#   "@(#)$Id: echopath.pl,v 1.7 1998/09/15 03:16:36 jleffler Exp $"
#
#   Print the components of a PATH variable one per line.
#   If there are no colons in the arguments, assume that they are
#   the names of environment variables.

@ARGV = $ENV{PATH} unless @ARGV;

foreach $arg (@ARGV)
{
    $var = $arg;
    $var = $ENV{$arg} if $arg =~ /^[A-Za-z_][A-Za-z_0-9]*$/;
    $var = $arg unless $var;
    @lst = split /:/, $var;
    foreach $val (@lst)
    {
            print "$val\n";
    }
}

Quando eu executar a solução modificada no código de teste abaixo:

echo
xpath=$PATH
replace_path xpath $xpath /usr
echopath $xpath

echo
xpath=$PATH
replace_path_pattern xpath $xpath /usr/bin /work/bin
echopath xpath

echo
xpath=$PATH
replace_path_pattern xpath $xpath "/usr/.*/bin" /work/bin
echopath xpath

A saída é:

.
/Users/jleffler/bin
/usr/local/postgresql/bin
/usr/local/mysql/bin
/Users/jleffler/perl/v5.10.0/bin
/usr/local/bin
/usr/bin
/bin
/sw/bin
/usr/sbin
/sbin

.
/Users/jleffler/bin
/usr/local/postgresql/bin
/usr/local/mysql/bin
/Users/jleffler/perl/v5.10.0/bin
/usr/local/bin
/work/bin
/bin
/sw/bin
/usr/sbin
/sbin

.
/Users/jleffler/bin
/work/bin
/usr/local/mysql/bin
/Users/jleffler/perl/v5.10.0/bin
/usr/local/bin
/usr/bin
/bin
/sw/bin
/usr/sbin
/sbin

Este parece correto para mim - pelo menos, para a minha definição de qual é o problema

.

noto que avalia echopath LD_LIBRARY_PATH $LD_LIBRARY_PATH. Seria bom se suas funções foram capazes de fazer isso, então o usuário pode digitar:

replace_path PATH /usr/bin /work/bin

Isso pode ser feito usando-se:

list=$(eval echo '$'$path)

Isto leva a esta revisão do código:

# path_tools.bash
#
# A set of tools for manipulating ":" separated lists like the
# canonical $PATH variable.
#
# /bin/sh compatibility can probably be regained by replacing $( )
# style command expansion with ` ` style
###############################################################################
# Usage:
#
# To remove a path:
#    replace_path         PATH /exact/path/to/remove
#    replace_path_pattern PATH <grep pattern for target path>
#
# To replace a path:
#    replace_path         PATH /exact/path/to/remove /replacement/path
#    replace_path_pattern PATH <target pattern> /replacement/path
#
###############################################################################

# Remove or replace an element of $1
#
#   $1 name of the shell variable to set (e.g. PATH)
#   $2 the precise string to be removed/replaced
#   $3 the replacement string (use "" for removal)
function replace_path () {
    path=$1
    list=$(eval echo '$'$path)
    remove=$2
    replace=$3            # Allowed to be empty or unset

    export $path=$(echo "$list" | tr ":" "\n" | sed "s:^$remove\$:$replace:" |
                   tr "\n" ":" | sed 's|:$||')
}

# Remove or replace an element of $1
#
#   $1 name of the shell variable to set (e.g. PATH)
#   $2 a grep pattern identifying the element to be removed/replaced
#   $3 the replacement string (use "" for removal)
function replace_path_pattern () {
    path=$1
    list=$(eval echo '$'$path)
    removepat=$2
    replacestr=$3            # Allowed to be empty or unset

    removestr=$(echo "$list" | tr ":" "\n" | grep -m 1 "^$removepat\$")
    replace_path "$path" "$removestr" "$replacestr"
}

O teste a seguir revisto agora trabalha demasiado:

echo
xpath=$PATH
replace_path xpath /usr
echopath xpath

echo
xpath=$PATH
replace_path_pattern xpath /usr/bin /work/bin
echopath xpath

echo
xpath=$PATH
replace_path_pattern xpath "/usr/.*/bin" /work/bin
echopath xpath

Ela produz a mesma saída como antes.

Outras dicas

Reposting minha resposta a o que é a maneira mais elegante para remover um caminho a partir da variável $ pATH em Bash :?

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

ou o one-liner:

PATH=$(IFS=':';t=($PATH);unset IFS;t=(${t[@]%%*usr*});IFS=':';echo "${t[*]}");

Para excluir um elemento que você pode usar sed:

#!/bin/bash
NEW_PATH=$(echo -n $PATH | tr ":" "\n" | sed "/foo/d" | tr "\n" ":")
export PATH=$NEW_PATH

excluirá os caminhos que contêm "foo" do caminho.

Você também pode usar sed para inserir uma nova linha antes ou depois de uma determinada linha.

Edit: você pode remover duplicatas, canalizando através de classificar e uniq:

echo -n $PATH | tr ":" "\n" | sort | uniq -c | sed -n "/ 1 / s/.*1 \(.*\)/\1/p" | sed "/foo/d" | tr "\n" ":"

Há um par de programas relevantes nas respostas às " Como para não duplicar variável caminho na csh ". Eles se concentrar mais em garantir que não existem elementos repetidos, mas o script eu fornecer pode ser usado como:

export PATH=$(clnpath $head_dirs:$PATH:$tail_dirs $remove_dirs)

Supondo que você tenha um ou mais diretórios em $ head_dirs e um ou mais diretórios em $ tail_dirs e um ou mais diretórios em $ remove_dirs, em seguida, ele usa o shell para concatenar partes da cabeça, atuais e cauda em um valor enorme, e em seguida, remove cada um dos diretórios listados em $ remove_dirs do resultado (não um erro se eles não existem), bem como a eliminação de segundo e ocorrências posteriores de qualquer diretório no caminho.

Esta não aborda colocar componentes de caminho em uma posição específica (que não seja no início ou no fim, e aqueles que apenas indiretamente). Notationally, especificando onde pretende adicionar o novo elemento, ou qual elemento você deseja substituir, é confuso.

Apenas uma nota que bater em si pode não procurar e substituir. Ele pode fazer tudo o normal "uma ou todas", casos [no] opções sensíveis que você esperaria.

A partir da página man:

$ {parâmetro / padrão / string}

O padrão é expandida para produzir um padrão assim como na expansão de nome. Parâmetro é expandido e mais longa partida de padrão contra o seu valor é substituído com corda. Se Ipattern começa com /, todos os jogos do padrão são substituídas por cordas. Normalmente, apenas o primeiro jogo é substituído. Se o padrão começa com #, ele deve corresponder no início do valor expandido do parâmetro. Se o padrão começa com%, ele deve corresponder ao final do valor expandido do parâmetro. Se a string é null, jogos de padrão são apagados e o seguinte padrão / pode ser omitido. Se o parâmetro é @ ou *, A operação de substituição é aplicada a cada um dos parâmetros de posição, por sua vez, e a expansão é a lista resultante. Se o parâmetro é uma variável de matriz subscritos com @ ou *, A operação de substituição é aplicada a cada membro da matriz, por sua vez, e a expansão é a lista resultante.

Você também pode fazer a divisão campo pela configuração $ IFS (separador de campos de entrada) para o delimitador desejado.

OK, graças a todos os respondedores. Eu preparei uma versão encapsulada de resposta de florin. A primeira passagem esta aparência:

# path_tools.bash
#
# A set of tools for manipulating ":" separated lists like the
# canonical $PATH variable.
#
# /bin/sh compatibility can probably be regained by replacing $( )
# style command expansion with ` ` style
###############################################################################
# Usage:
#
# To remove a path:
#    replace-path         PATH $PATH /exact/path/to/remove    
#    replace-path-pattern PATH $PATH <grep pattern for target path>    
#
# To replace a path:
#    replace-path         PATH $PATH /exact/path/to/remove /replacement/path   
#    replace-path-pattern PATH $PATH <target pattern> /replacement/path
#    
###############################################################################
# Finds the _first_ list element matching $2
#
#    $1 name of a shell variable to be set
#    $2 name of a variable with a path-like structure
#    $3 a grep pattern to match the desired element of $1
function path-element-by-pattern (){ 
    target=$1;
    list=$2;
    pat=$3;

    export $target=$(echo -n $list | tr ":" "\n" | grep -m 1 $pat);
    return
}

# Removes or replaces an element of $1
#
#   $1 name of the shell variable to set (i.e. PATH) 
#   $2 a ":" delimited list to work from (i.e. $PATH)
#   $2 the precise string to be removed/replaced
#   $3 the replacement string (use "" for removal)
function replace-path () {
    path=$1;
    list=$2;
    removestr=$3;
    replacestr=$4; # Allowed to be ""

    export $path=$(echo -n $list | tr ":" "\n" | sed "s|$removestr|$replacestr|" | tr "\n" ":" | sed "s|::|:|g");
    unset removestr
    return 
}

# Removes or replaces an element of $1
#
#   $1 name of the shell variable to set (i.e. PATH) 
#   $2 a ":" delimited list to work from (i.e. $PATH)
#   $2 a grep pattern identifying the element to be removed/replaced
#   $3 the replacement string (use "" for removal)
function replace-path-pattern () {
    path=$1;
    list=$2;
    removepat=$3; 
    replacestr=$4; # Allowed to be ""

    path-element-by-pattern removestr $list $removepat;
    replace-path $path $list $removestr $replacestr;
}

Ainda precisa de interceptação de erro em todas as funções, e eu provavelmente deve ficar em uma solução caminho repetido enquanto eu estou nele.

Você usá-lo fazendo uma . /include/path/path_tools.bash no script trabalhando e convidando das funções replace-path*.


Eu ainda estou aberto a novas e / ou melhores respostas.

Esta é fácil usando awk.

Substitua

{
  for(i=1;i<=NF;i++) 
      if($i == REM) 
          if(REP)
              print REP; 
          else
              continue;
      else 
          print $i; 
}

Iniciar-lo usando

function path_repl {
    echo $PATH | awk -F: -f rem.awk REM="$1" REP="$2" | paste -sd:
}

$ echo $PATH
/bin:/usr/bin:/home/js/usr/bin
$ path_repl /bin /baz
/baz:/usr/bin:/home/js/usr/bin
$ path_repl /bin
/usr/bin:/home/js/usr/bin

Anexar

Inserido na posição dada. Por padrão, ele acrescenta no final.

{ 
    if(IDX < 1) IDX = NF + IDX + 1
    for(i = 1; i <= NF; i++) {
        if(IDX == i) 
            print REP 
        print $i
    }
    if(IDX == NF + 1)
        print REP
}

Iniciar-lo usando

function path_app {
    echo $PATH | awk -F: -f app.awk REP="$1" IDX="$2" | paste -sd:
}

$ echo $PATH
/bin:/usr/bin:/home/js/usr/bin
$ path_app /baz 0
/bin:/usr/bin:/home/js/usr/bin:/baz
$ path_app /baz -1
/bin:/usr/bin:/baz:/home/js/usr/bin
$ path_app /baz 1
/baz:/bin:/usr/bin:/home/js/usr/bin

Remover duplicatas

Este mantém as primeiras ocorrências.

{ 
    for(i = 1; i <= NF; i++) {
        if(!used[$i]) {
            print $i
            used[$i] = 1
        }
    }
}

Iniciar assim:

echo $PATH | awk -F: -f rem_dup.awk | paste -sd:

Validar se existem todos os elementos

A seguir irá imprimir uma mensagem de erro para todas as entradas que não são existentes no sistema de arquivos, e retornar um valor diferente de zero.

echo -n $PATH | xargs -d: stat -c %n

Para verificar simplesmente se todos os elementos são caminhos e obter um código de retorno, você também pode usar test:

echo -n $PATH | xargs -d: -n1 test -d

supor

echo $PATH
/usr/lib/jvm/java-1.6.0/bin:lib/jvm/java-1.6.0/bin/:/lib/jvm/java-1.6.0/bin/:/usr/lib/qt-3.3/bin:/usr/lib/ccache:/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin:/home/tvnadeesh/bin

Se você deseja remover /lib/jvm/java-1.6.0/bin/ gosto como abaixo

export PATH=$(echo $PATH | sed  's/\/lib\/jvm\/java-1.6.0\/bin\/://g')

sed terá a entrada de echo $PATH e substituir /lib/jvm/java-1.6.0/bin/: com um vazio

Desta forma, você pode remover

  • Ordem do caminho não é distrubed
  • casos alças de canto como caminho vazio, o espaço no caminho graciosamente
  • jogo parcial de dir não dá falsos positivos
  • caminho Treats a cabeça ea cauda da PATH em formas adequadas. Não : lixo e tal
  • .

Digamos que você tenha / Foo: / algum caminho /: / some / path / dir1: / some / path / dir2: / bar e você deseja substituir / Algum caminho / Em seguida, ele corretamente substitui "/ some / path", mas folhas "/ some / path / dir1" ou "/ some / path / Dir2", como o que você esperaria.

function __path_add(){  
    if [ -d "$1" ] ; then  
        local D=":${PATH}:";   
        [ "${D/:$1:/:}" == "$D" ] && PATH="$PATH:$1";  
        PATH="${PATH/#:/}";  
        export PATH="${PATH/%:/}";  
    fi  
}
function __path_remove(){  
    local D=":${PATH}:";  
    [ "${D/:$1:/:}" != "$D" ] && PATH="${D/:$1:/:}";  
    PATH="${PATH/#:/}";  
    export PATH="${PATH/%:/}";  
}  
# Just for the shake of completeness
function __path_replace(){  
    if [ -d "$2" ] ; then  
        local D=":${PATH}:";   
        if [ "${D/:$1:/:}" != "$D" ] ; then
            PATH="${D/:$1:/:$2:}";  
            PATH="${PATH/#:/}";  
            export PATH="${PATH/%:/}";  
        fi
    fi  
}  

post relacionado o que é a maneira mais elegante para remover um caminho a partir da variável $ pATH em Bash?

Eu prefiro usar ruby ??para os gostos de awk / sed / foo estes dias, então aqui está a minha abordagem para lidar com dupes,

# add it to the path
PATH=~/bin/:$PATH:~/bin
export PATH=$(ruby -e 'puts ENV["PATH"].split(/:/).uniq.join(":")')

criar uma função para reutilização,

mungepath() {
   export PATH=$(ruby -e 'puts ENV["PATH"].split(/:/).uniq.join(":")')
}

Hash, matrizes e cordas em um forro rubi um:)

A primeira coisa a estalar em minha cabeça para mudar apenas uma parte de uma string é uma substituição sed.

exemplo: se echo $ PATH => "/ usr / pkg / bin: / usr / bin: / bin: / usr / pkg / jogos: / usr / pkg / X11R6 / bin" em seguida, a mudança "/ usr / bin" para "/ usr / local / bin" poderia ser feito assim:

## produz arquivo de saída padrão

## o caractere "=" é usado em vez de barra ( "/"), uma vez que seria confuso, # Alternativa citando personagem deve ser improvável no PATH

## o caráter separater caminho ":" é tanto removido e re-adicionado aqui, # Pode querer um cólon extra após o último caminho

echo $ PATH | sed '= / usr / bin: = / usr / / bin local: ='

Esta solução substitui todo um caminho-element isso pode ser redundante, se novo elemento é semelhante.

Se os novos PATH'-s não são dinâmica, mas sempre dentro de alguma constante definido você pode salvar os em uma variável e atribuir conforme necessário:

PATH = $ TEMP_PATH_1; # Comandos ...; \ n PATH = $ TEMP_PATH_2; # Comandos etc ...;

Pode não ser o que você estava pensando. alguns dos comandos relevantes sobre bash / unix seria:

pushd popd CD ls # talvez l -1A para única coluna; encontrar grep que # poderia confirmar esse arquivo é onde você acha que ele veio; env Tipo

.. e tudo o que e mais têm alguns influência sobre PATH ou diretórios em geral. A parte do texto alterando poderia ser feito de várias maneiras!

solução Seja qual for o escolhido teria 4 partes:

1) buscar o caminho, pois é 2) descodificar o caminho para encontrar a parte necessitando alterações 3) determing que mudanças são necessárias / integrando essas mudanças 4) validação / integração final / definindo a variável

De acordo com a de dj_segfault resposta , eu faço isso em scripts que as variáveis ??de acréscimo / preceder ambiente que pode ser executado várias vezes :

ld_library_path=${ORACLE_HOME}/lib
LD_LIBRARY_PATH=${LD_LIBRARY_PATH//${ld_library_path}?(:)/}
export LD_LIBRARY_PATH=${ld_library_path}${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}

Usando a mesma técnica para remover, substituir ou manipular entradas no PATH é trivial dado o nome do arquivo-expansão-como correspondência de padrão e padrão de lista de suporte de expansão de parâmetros shell.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top