Estou tentando escrever uma função de shell bash que me permita remover cópias duplicadas de diretórios da minha variável de ambiente PATH.
Disseram-me que é possível conseguir isso com um comando de uma linha usando o awk
comando, mas não consigo descobrir como fazê-lo. Alguém sabe como?
Respostas:
Se você ainda não possui duplicatas no
PATH
diretório e deseja adicionar diretórios apenas se eles ainda não estiverem lá, é possível fazê-lo facilmente apenas com o shell.E aqui está um trecho de shell que remove duplicatas
$PATH
. Ele percorre as entradas uma a uma e copia as que ainda não foram vistas.fonte
PATH=$PATH:x=b
o x no PATH original pode ter o valor a, portanto, quando iterar em ordem, o novo valor será ignorado, mas quando em ordem inversa, o novo valor entrará em vigor.PATH=x:$PATH
.PATH=$PATH:...
nãoPATH=...:$PATH
. Portanto, é mais apropriado repetir a ordem inversa. Mesmo que você também trabalhe, as pessoas se juntam da maneira inversa.Aqui está uma solução inteligente de uma linha que faz todas as coisas certas: remove duplicatas, preserva a ordem dos caminhos e não adiciona dois pontos no final. Portanto, ele deve fornecer um caminho deduplicado que oferece exatamente o mesmo comportamento que o original:
Simplesmente divide em dois pontos (
split(/:/, $ENV{PATH})
), usa usosgrep { not $seen{$_}++ }
para filtrar quaisquer instâncias repetidas de caminhos, exceto a primeira ocorrência, e depois junta as demais, separadas por dois pontos e imprime o resultado (print join(":", ...)
).Se você quiser um pouco mais de estrutura em torno dele, bem como a capacidade de deduplicar outras variáveis também, tente este snippet, que estou usando atualmente em minha própria configuração:
Esse código deduplicará PATH e MANPATH, e você poderá facilmente chamar
dedup_pathvar
outras variáveis que contêm listas de caminhos separados por dois pontos (por exemplo, PYTHONPATH).fonte
chomp
para remover uma nova linha à direita. Isso funcionou para mim:perl -ne 'chomp; print join(":", grep { !$seen{$_}++ } split(/:/))' <<<"$PATH"
Aqui está um elegante:
Mais (para ver como funciona):
Ok, já que você é novo no linux, aqui está como realmente definir PATH sem um ":" à direita
btw certifique-se de NÃO ter diretórios contendo ":" no seu PATH, caso contrário, será confuso.
algum crédito para:
fonte
echo -n
. Seus comandos não parecem trabalhar com "aqui cordas" por exemplo, tente:awk -v RS=: -v ORS=: '!arr[$0]++' <<< ".:/foo/bin:/bar/bin:/foo/bin"
Aqui está um liner AWK one.
Onde:
printf %s "$PATH"
imprime o conteúdo$PATH
sem uma nova linha à direitaRS=:
altera o caractere delimitador do registro de entrada (o padrão é nova linha)ORS=
altera o delimitador do registro de saída para a sequência vaziaa
o nome de uma matriz criada implicitamente$0
referencia o registro atuala[$0]
é uma desreferência de matriz associativa++
é o operador pós-incremento!a[$0]++
protege o lado direito, ou seja, garante que o registro atual seja impresso apenas se não tiver sido impresso antesNR
o número do registro atual, começando com 1Isso significa que o AWK é usado para dividir o
PATH
conteúdo pelos:
caracteres delimitadores e para filtrar entradas duplicadas sem modificar a ordem.Como as matrizes associativas do AWK são implementadas como tabelas de hash, o tempo de execução é linear (ou seja, em O (n)).
Observe que não precisamos procurar
:
caracteres entre aspas , porque os shells não fornecem aspas para suportar diretórios:
em seu nome naPATH
variável.Awk + colar
O acima pode ser simplificado com pasta:
O
paste
comando é usado para intercalar a saída do awk com dois pontos. Isso simplifica a ação awk da impressão (que é a ação padrão).Pitão
O mesmo que o Python de duas linhas:
fonte
paste
comando não funciona para mim, a menos que eu adicione um final-
para usar STDIN.-v
erro, caso contrário.-v RS=: -v ORS=
. Apenas diferentes tipos deawk
sintaxe.Houve uma discussão semelhante sobre isso aqui .
Eu adoto uma abordagem um pouco diferente. Em vez de aceitar apenas o PATH definido em todos os diferentes arquivos de inicialização instalados, prefiro usar
getconf
para identificar o caminho do sistema e colocá-lo primeiro, depois adicionar minha ordem de caminho preferida e depoisawk
remover as duplicatas. Isso pode ou não acelerar a execução de comandos (e, em teoria, ser mais seguro), mas isso me deixa confuso.fonte
:
àPATH
(ou seja, uma entrada de sequência vazia), porque o diretório de trabalho atual faz parte do seuPATH
.Enquanto estivermos adicionando oneliners não awk:
(Pode ser tão simples quanto
PATH=$(zsh -fc 'typeset -U path; echo $PATH')
mas o zsh sempre lê pelo menos umzshenv
arquivo de configuração, que pode ser modificadoPATH
.)Ele usa dois recursos interessantes do zsh:
typeset -T
)typeset -U
).fonte
Isso usa perl e tem vários benefícios:
/usr/bin:/sbin:/usr/bin
resultará em/usr/bin:/sbin
)fonte
Também
sed
(aqui usando ased
sintaxe GNU ) pode fazer o trabalho:este funciona bem apenas se o primeiro caminho for
.
como no exemplo do dogbane.Em geral, você precisa adicionar outro
s
comando:Ele funciona mesmo em tal construção:
fonte
Como outros demonstraram, é possível em uma linha usar awk, sed, perl, zsh ou bash, depende da sua tolerância para linhas longas e legibilidade. Aqui está uma função bash que
função bash
uso
Para remover dups do PATH
fonte
Esta é a minha versão:
Uso:
path_no_dup "$PATH"
Saída de amostra:
fonte
Versões recentes do bash (> = 4) também de matrizes associativas, ou seja, você também pode usar um bash 'one liner' para ele:
Onde:
IFS
altera o separador do campo de entrada para:
declare -A
declara uma matriz associativa${a[$i]+_}
é um significado de expansão de parâmetro:_
é substituído se e somente sea[$i]
estiver definido. Isso é semelhante ao${parameter:+word}
qual também testa não-nulo. Assim, na seguinte avaliação do condicional, a expressão_
(ou seja, uma única cadeia de caracteres) é avaliada como verdadeira (isso é equivalente a-n _
) - enquanto uma expressão vazia é avaliada como falsa.fonte
${a[$i]+_}
editando sua resposta e adicionando um marcador. O resto é perfeitamente compreensível, mas você me perdeu lá. Obrigado.Explicação do código awk:
Além de concisa, essa linha única é rápida: o awk usa uma tabela de hash de encadeamento para obter o desempenho O (1) amortizado.
com base em Removendo entradas duplicadas $ PATH
fonte
if ( !x[$i]++ )
. Obrigado.Use
awk
para dividir o caminho:
, faça um loop sobre cada campo e armazene-o em uma matriz. Se você se deparar com um campo que já está na matriz, significa que já o viu antes, portanto, não imprima.Aqui está um exemplo:
(Atualizado para remover o final
:
).fonte
Uma solução - não tão elegante quanto aquelas que alteram as variáveis * RS, mas talvez razoavelmente clara:
Todo o programa funciona nos blocos BEGIN e END . Ele puxa sua variável PATH do ambiente, dividindo-a em unidades. Ele itera sobre a matriz resultante p (que é criada em ordem por
split()
). A matriz e é uma matriz associativa usada para determinar se vimos ou não o elemento do caminho atual (por exemplo, / usr / local / bin ) antes e, se não, é anexado ao np , com lógica para anexar dois pontos ao np se já houver texto em np . O bloco END simplesmente ecoa np . Isso poderia ser mais simplificado adicionando o-F:
flag, eliminando o terceiro argumento parasplit()
(como padrão para FS ) e alterandonp = np ":"
paranp = np FS
, fornecendo-nos:Ingenuamente, eu acreditava que
for(element in array)
isso preservaria a ordem, mas não funciona, então minha solução original não funciona, pois as pessoas ficariam chateadas se alguém de repente mexesse na ordem das suas$PATH
:fonte
Somente a primeira ocorrência é mantida e a ordem relativa é bem mantida.
fonte
Eu faria isso apenas com ferramentas básicas como tr, sort e uniq:
Se não houver nada de especial ou estranho no seu caminho, ele deve funcionar
fonte
sort -u
vez desort | uniq
.