Como faço para manipular $ elementos caminho no shell scripts?
-
07-07-2019 - |
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.
- Anterior pergunta semelhante: Como evitar a duplicação de variável de caminho em csh
- pergunta semelhante subsequente: o que é a maneira mais elegante para remover um caminho a partir da variável $ pATH em Bash?
Solução
Dirigindo-se a solução proposta de dmckee:
- Embora algumas versões do Bash pode permitir hífens em nomes de função, outros (MacOS X) não.
- Não vejo a necessidade de utilização de retorno imediatamente antes do fim da função.
- Não vejo a necessidade de todos os pontos e vírgulas.
- 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 - 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.