Remova entradas $PATH duplicadas com o comando awk

Remova entradas $PATH duplicadas com o comando awk

Estou tentando escrever uma função shell bash que me permitirá 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 awkcomando, mas não consigo descobrir como fazê-lo. Alguém sabe como?

Responder1

Se você ainda não possui duplicatas no PATHe deseja adicionar diretórios apenas se eles ainda não estiverem lá, você pode fazer isso facilmente apenas com o shell.

for x in /path/to/add …; do
  case ":$PATH:" in
    *":$x:"*) :;; # already there
    *) PATH="$x:$PATH";;
  esac
done

E aqui está um trecho de shell que remove duplicatas de arquivos $PATH. Ele percorre as entradas uma por uma e copia aquelas que ainda não foram vistas.

if [ -n "$PATH" ]; then
  old_PATH=$PATH:; PATH=
  while [ -n "$old_PATH" ]; do
    x=${old_PATH%%:*}       # the first remaining entry
    case $PATH: in
      *:"$x":*) ;;          # already there
      *) PATH=$PATH:$x;;    # not there yet
    esac
    old_PATH=${old_PATH#*:}
  done
  PATH=${PATH#:}
  unset old_PATH x
fi

Responder2

Aqui está uminteligívelsolução 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 PATH desduplicado que forneça exatamente o mesmo comportamento do original:

PATH="$(perl -e 'print join(":", grep { not $seen{$_}++ } split(/:/, $ENV{PATH}))')"

Ele simplesmente divide em dois pontos ( split(/:/, $ENV{PATH})), usa usos grep { not $seen{$_}++ }para filtrar quaisquer instâncias repetidas de caminhos, exceto a primeira ocorrência, e então junta os restantes novamente separados por dois pontos e imprime o resultado ( print join(":", ...)).

Se você quiser mais estrutura em torno disso, bem como a capacidade de desduplicar outras variáveis, tente este trecho, que estou usando atualmente em minha própria configuração:

# Deduplicate path variables
get_var () {
    eval 'printf "%s\n" "${'"$1"'}"'
}
set_var () {
    eval "$1=\"\$2\""
}
dedup_pathvar () {
    pathvar_name="$1"
    pathvar_value="$(get_var "$pathvar_name")"
    deduped_path="$(perl -e 'print join(":",grep { not $seen{$_}++ } split(/:/, $ARGV[0]))' "$pathvar_value")"
    set_var "$pathvar_name" "$deduped_path"
}
dedup_pathvar PATH
dedup_pathvar MANPATH

Esse código desduplicará PATH e MANPATH, e você poderá facilmente chamar dedup_pathvaroutras variáveis ​​que contenham listas de caminhos separados por dois pontos (por exemplo, PYTHONPATH).

Responder3

Aqui está um elegante:

printf %s "$PATH" | awk -v RS=: -v ORS=: '!arr[$0]++'

Mais (para ver como funciona):

printf %s "$PATH" | awk -v RS=: -v ORS=: '{ if (!arr[$0]++) { print $0 } }'

Ok, já que você é novo no Linux, aqui está como definir PATH sem um ":" final

PATH=`printf %s "$PATH" | awk -v RS=: '{ if (!arr[$0]++) {printf("%s%s",!ln++?"":":",$0)}}'`

aliás, certifique-se de NÃO ter diretórios contendo ":" em seu PATH, caso contrário, ficará confuso.

algum crédito para:

Responder4

Contanto que adicionemos oneliners não awk:

PATH=$(zsh -fc "typeset -TU P=$PATH p; echo \$P")

(Pode ser tão simples quanto PATH=$(zsh -fc 'typeset -U path; echo $PATH')zsh sempre lê pelo menos um zshenvarquivo de configuração, que pode modificar PATH.)

Ele usa dois recursos interessantes do zsh:

  • escalares vinculados a matrizes ( typeset -T)
  • e matrizes que removem automaticamente valores duplicados ( typeset -U).

informação relacionada