Estoy tratando de escribir una función de shell bash que me permita eliminar copias duplicadas de directorios de mi variable de entorno PATH.
Me dijeron que es posible lograr esto con un comando de una línea usando el awk
comando, pero no puedo entender cómo hacerlo. Alguien sabe como?
Respuestas:
Si aún no tiene duplicados en el
PATH
y solo desea agregar directorios si aún no están allí, puede hacerlo fácilmente solo con el shell.Y aquí hay un fragmento de shell que elimina los duplicados de
$PATH
. Revisa las entradas una por una y copia las que aún no se han visto.fuente
PATH=$PATH:x=b
la x en PATH original podría tener el valor a, por lo tanto, cuando se itera en orden, el nuevo valor se ignorará, pero cuando esté en orden inverso, el nuevo El valor tendrá efecto.PATH=x:$PATH
.PATH=$PATH:...
noPATH=...:$PATH
. Por lo tanto, es más apropiado iterar el orden inverso. Aunque tu camino también funcionaría, entonces las personas agregan en el camino inverso.Aquí hay una solución inteligible de una sola línea que hace todo lo correcto: elimina los duplicados, conserva el orden de las rutas y no agrega dos puntos al final. Por lo tanto, debería proporcionarle una RUTA deduplicada que ofrezca exactamente el mismo comportamiento que el original:
Simplemente se divide en dos puntos (
split(/:/, $ENV{PATH})
), utiliza los usosgrep { not $seen{$_}++ }
para filtrar cualquier instancia repetida de rutas, excepto la primera aparición, y luego une las restantes de nuevo separadas por dos puntos e imprime el resultado (print join(":", ...)
).Si desea algo más de estructura a su alrededor, así como también la capacidad de deduplicar otras variables, pruebe este fragmento, que actualmente estoy usando en mi propia configuración:
Ese código deduplicará tanto PATH como MANPATH, y puede invocar fácilmente
dedup_pathvar
otras variables que contienen listas de rutas separadas por dos puntos (por ejemplo, PYTHONPATH).fuente
chomp
para eliminar una nueva línea final. Esto funcionó para mí:perl -ne 'chomp; print join(":", grep { !$seen{$_}++ } split(/:/))' <<<"$PATH"
Aquí hay uno elegante:
Más tiempo (para ver cómo funciona):
Ok, como eres nuevo en Linux, aquí es cómo configurar la RUTA sin un ":" final
por cierto, asegúrese de NO tener directorios que contengan ":" en su RUTA, de lo contrario, será un desastre.
algún crédito a:
fuente
echo -n
. Sus comandos no parecen funcionar con "cadenas aquí", por ejemplo, intente:awk -v RS=: -v ORS=: '!arr[$0]++' <<< ".:/foo/bin:/bar/bin:/foo/bin"
Aquí hay un AWK one liner.
dónde:
printf %s "$PATH"
imprime el contenido$PATH
sin una nueva línea finalRS=:
cambia el carácter delimitador del registro de entrada (el valor predeterminado es nueva línea)ORS=
cambia el delimitador de registro de salida a la cadena vacíaa
el nombre de una matriz creada implícitamente$0
hace referencia al registro actuala[$0]
es una desreferencia de matriz asociativa++
es el operador posterior al incremento!a[$0]++
protege el lado derecho, es decir, se asegura de que el registro actual solo se imprima, si no se imprimió antesNR
el número de registro actual, comenzando con 1Eso significa que AWK se usa para dividir el
PATH
contenido a lo largo de los:
caracteres delimitadores y para filtrar entradas duplicadas sin modificar el orden.Dado que las matrices asociativas AWK se implementan como tablas hash, el tiempo de ejecución es lineal (es decir, en O (n)).
Tenga en cuenta que no necesitamos buscar
:
caracteres entre comillas porque los shells no proporcionan comillas para admitir directorios con:
su nombre en laPATH
variable.Awk + pegar
Lo anterior se puede simplificar con pegar:
El
paste
comando se utiliza para intercalar la salida awk con dos puntos. Esto simplifica la acción awk para imprimir (que es la acción predeterminada).Pitón
Lo mismo que Python de dos líneas:
fuente
paste
comando no funciona para mí a menos que agregue un final-
para usar STDIN.-v
o, de lo contrario, obtengo un error.-v RS=: -v ORS=
. Simplemente diferentes sabores deawk
sintaxis.Ha habido una discusión similar sobre esto aquí .
Tomo un enfoque un poco diferente. En lugar de simplemente aceptar la RUTA que se establece a partir de todos los diferentes archivos de inicialización que se instalan, prefiero usar
getconf
para identificar la ruta del sistema y colocarla primero, luego agregar mi orden de ruta preferida, luego usarawk
para eliminar cualquier duplicado. Esto puede o no acelerar realmente la ejecución del comando (y en teoría ser más seguro), pero me da calidez.fuente
:
alPATH
(es decir, una entrada de cadena vacía), porque el directorio de trabajo actual es parte de suPATH
.Siempre y cuando estemos agregando líneas no awk:
(Podría ser tan simple como
PATH=$(zsh -fc 'typeset -U path; echo $PATH')
pero zsh siempre lee al menos unzshenv
archivo de configuración, que puede modificarPATH
).Utiliza dos características agradables de zsh:
typeset -T
)typeset -U
).fuente
Esto usa perl y tiene varios beneficios:
/usr/bin:/sbin:/usr/bin
resultará en/usr/bin:/sbin
)fuente
Además
sed
(aquí usando lased
sintaxis de GNU ) puede hacer el trabajo:este funciona bien solo en caso de que el primer camino sea
.
como en el ejemplo de dogbane.En general, debe agregar otro
s
comando más:Funciona incluso en tal construcción:
fuente
Como otros han demostrado, es posible en una línea usar awk, sed, perl, zsh o bash, depende de su tolerancia para líneas largas y legibilidad. Aquí hay una función bash que
función bash
uso
Para eliminar dups de PATH
fuente
Esta es mi versión
Uso:
path_no_dup "$PATH"
Salida de muestra:
fuente
Las versiones recientes de bash (> = 4) también de matrices asociativas, es decir, también puede usar un bash 'one liner' para ello:
dónde:
IFS
cambia el separador de campo de entrada a:
declare -A
declara una matriz asociativa${a[$i]+_}
es un significado de expansión de parámetro:_
se sustituye si y solo sia[$i]
está configurado. Esto es similar a lo${parameter:+word}
que también prueba para no nulo. Por lo tanto, en la siguiente evaluación del condicional, la expresión_
(es decir, una sola cadena de caracteres) se evalúa como verdadera (esto es equivalente a-n _
), mientras que una expresión vacía se evalúa como falsa.fuente
${a[$i]+_}
Editando tu respuesta y agregando una viñeta. El resto es perfectamente comprensible, pero me perdiste allí. Gracias.Explicación del código awk:
Además de ser conciso, este one-liner es rápido: awk utiliza una tabla hash de encadenamiento para lograr un rendimiento amortiguado de O (1).
basado en la eliminación de entradas duplicadas de $ PATH
fuente
if ( !x[$i]++ )
. Gracias.Use
awk
para dividir la ruta:
, luego repita sobre cada campo y almacénelo en una matriz. Si se encuentra con un campo que ya está en la matriz, eso significa que lo ha visto antes, así que no lo imprima.Aquí hay un ejemplo:
(Actualizado para eliminar el final
:
).fuente
Una solución, no tan elegante como las que cambian las variables * RS, pero quizás razonablemente clara:
Todo el programa funciona en los bloques BEGIN y END . Extrae su variable PATH del entorno, dividiéndola en unidades. Luego itera sobre la matriz resultante p (que se crea en orden por
split()
). La matriz e es una matriz asociativa que se usa para determinar si hemos visto o no el elemento de ruta actual (por ejemplo, / usr / local / bin ) y, si no, se agrega a np , con lógica para agregar dos puntos a np si ya hay texto en np . El bloque END simplemente echos np . Esto podría simplificarse aún más agregando el-F:
flag, eliminando el tercer argumento parasplit()
(como el valor predeterminado es FS ), y cambiandonp = np ":"
anp = np FS
, dándonos:Ingenuamente, creía que eso
for(element in array)
preservaría el orden, pero no lo hace, por lo que mi solución original no funciona, ya que la gente se molestaría si alguien de repente revuelve el orden de sus$PATH
:fuente
Solo se mantiene la primera aparición y se mantiene bien el orden relativo.
fuente
Lo haría solo con herramientas básicas como tr, sort y uniq:
Si no hay nada especial o extraño en tu camino, debería funcionar
fuente
sort -u
lugar desort | uniq
.