¿Cómo puedo eliminar duplicados en mi .bash_history, preservando el orden?

61

Realmente disfruto usando control+rpara buscar recursivamente mi historial de comandos. He encontrado algunas buenas opciones que me gusta usar con él:

# ignore duplicate commands, ignore commands starting with a space
export HISTCONTROL=erasedups:ignorespace

# keep the last 5000 entries
export HISTSIZE=5000

# append to the history instead of overwriting (good for multiple connections)
shopt -s histappend

El único problema para mí es que erasedupssolo borra los duplicados secuenciales, de modo que con esta cadena de comandos:

ls
cd ~
ls

El lscomando en realidad se grabará dos veces. He pensado en ejecutar periódicamente w / cron:

cat .bash_history | sort | uniq > temp.txt
mv temp.txt .bash_history

Esto lograría eliminar los duplicados, pero desafortunadamente el orden no se conservaría. Si no hago sortel archivo primero, no creo que uniqpueda funcionar correctamente.

¿Cómo puedo eliminar duplicados en mi .bash_history, preservando el orden?

Crédito adicional:

¿Hay algún problema al sobrescribir el .bash_historyarchivo a través de un script? Por ejemplo, si elimina un archivo de registro de apache, creo que debe enviar una señal de nohup / reset killpara que vacíe su conexión con el archivo. Si ese es el caso con el .bash_historyarchivo, ¿tal vez podría usarlo pspara verificar y asegurarme de que no haya sesiones conectadas antes de ejecutar el script de filtrado?

cwd
fuente
3
Intente en ignoredupslugar de erasedupspor un tiempo y vea cómo funciona para usted.
jw013
1
No creo que bash tenga un identificador de archivo abierto en el archivo de historial: lo lee / escribe cuando es necesario, por lo que debería (tenga en cuenta que no lo he probado) ser seguro para sobrescribirlo desde otro lugar.
D_Bye
1
Acabo de aprender algo nuevo en la primera oración de tu pregunta. ¡Buen truco!
Ricardo
No puedo encontrar la página de manual para todas las opciones del historycomando. ¿Dónde debería estar mirando?
Jonathan Hartley
Las opciones de historial se encuentran en 'man bash', busque la sección 'comandos integrados de shell' y luego 'historial' debajo de eso.
Jonathan Hartley

Respuestas:

36

Ordenar la historia

Este comando funciona como sort|uniq, pero mantiene las líneas en su lugar

nl|sort -k 2|uniq -f 1|sort -n|cut -f 2

Básicamente, antecede a cada línea su número. Después del sort|uniqencendido, todas las líneas se vuelven a clasificar según su orden original (utilizando el campo de número de línea) y el campo de número de línea se elimina de las líneas.

Esta solución tiene el defecto de que no está definida qué representante de una clase de líneas iguales lo hará en la salida y, por lo tanto, su posición en la salida final no está definida. Sin embargo, si se elige al último representante, puede sortingresar una segunda tecla:

nl|sort -k2 -k 1,1nr|uniq -f1|sort -n|cut -f2

Administrar .bash_history

Para volver a leer y escribir el historial, puede usar history -ay history -wrespectivamente.

artistoex
fuente
66
Una versión de decorate-sort-undecorate , implementada con herramientas de shell. Agradable.
ire_and_curses
Con sort, el -rinterruptor siempre invierte el orden de clasificación. Pero esto no dará el resultado que tienes en mente. sortconsidera que las dos ocurrencias lsson idénticas al resultado de que, incluso cuando se invierte, el orden eventual depende del algoritmo de clasificación. Pero mira mi actualización para otra idea.
artistoex
1
En caso de que no desee modificar .bash_history, puede poner lo siguiente en .bashrc: alias history = 'history | ordenar -k2 -k 1,1nr | uniq -f 1 | sort -n '
Nathan
¿Qué hay nlal principio de cada línea de código? ¿No debería ser history?
AL
1
@AL nl agrega números de línea. El comando en su conjunto resuelve el problema general: eliminar duplicados mientras se preserva el orden. La entrada se lee desde stdin.
artistoex
49

Así que estaba buscando exactamente lo mismo después de estar molesto por los duplicados, y descubrí que si editaba mi ~ / .bash_profile (Mac) con:

export HISTCONTROL=ignoreboth:erasedups

Hace exactamente lo que quería, solo mantiene lo último de cualquier comando. ignorebothen realidad es como hacer ignorespace:ignoredupsy eso junto con hacer erasedupsel trabajo.

Al menos en mi terminal Mac con bash, este trabajo es perfecto. Lo encontré aquí en askubuntu.com .

duende
fuente
10
esta debería ser la respuesta correcta
MitchBroadhead
probado en Max OS X Yosemite y en Ubuntu 14_04
Ricardo
1
de acuerdo con @MitchBroadhead. esto resuelve el problema dentro de bash en sí mismo, sin trabajo de cron externo. probado en ubuntu 17.04 y 16.04 LTS
Georg Jung
también funciona en OpenBSD. Solo elimina dups de cualquier comando que agregue al archivo de historial, lo cual está bien para mí. Tiene el interesante efecto de acortar el archivo de historial al ingresar comandos que habían existido como duplicados antes. Ahora puedo hacer que mi archivo de historial sea más corto.
WeakPointer
1
Esto solo ignora los comandos duplicados consecutivos. Si alterna repetidamente entre dos comandos dados, su historial de bash se llenará de duplicados
Dylanthepiguy
16

Encontré esta solución en la naturaleza y probé:

awk '!x[$0]++'

La primera vez que se ve un valor específico de una línea ($ 0), el valor de x [$ 0] es cero.
El valor de cero se invierte !y se convierte en uno.
Una declaración que se evalúa como uno provoca la acción predeterminada, que es imprimir.

Por lo tanto, la primera vez que $0se ve un específico , se imprime.

La próxima vez (las repeticiones) el valor de x[$0]se ha incrementado,
su valor negado es cero y no se imprime una declaración que evalúe a cero.

Para mantener el último valor repetido, invierta el historial y use el mismo awk:

awk '!x[$0]++' ~/.bash_history                 # keep the first value repeated.

tac ~/.bash_history | awk '!x[$0]++' | tac     # keep the last.
Clayton Stanley
fuente
¡Guauu! Eso simplemente funcionó. Pero elimina todo menos la primera ocurrencia, supongo. Invertí el orden de las líneas usando Sublime Text antes de ejecutar esto. Ahora lo revertiré nuevamente para obtener un historial limpio con solo la última aparición de todos los duplicados que quedan. Gracias.
trss
¡Mira mi respuesta!
Ali Shakiba
Buena respuesta limpia y general (no restringida al caso de uso del historial) sin lanzar un subproceso de bazilion ;-)
JepZ
9

Extendiendo la respuesta de Clayton:

tac $HISTFILE | awk '!x[$0]++' | tac | sponge $HISTFILE

tacinvierta el archivo, asegúrese de haberlo instalado moreutilspara tenerlo spongedisponible; de ​​lo contrario, use un archivo temporal.

Ali Shakiba
fuente
1
Para aquellos en Mac, use brew install coreutilsy tenga en cuenta que todas las utilidades de GNU tienen un gantecedente para evitar confusiones con los comandos integrados de BSD para Mac (por ejemplo, gsed es GNU mientras sed es BSD). Así que usa gtac.
tralston
Necesitaba history -c e history -r para que use el historial
drescherjm
4

Estos mantendrían las últimas líneas duplicadas:

ruby -i -e 'puts readlines.reverse.uniq.reverse' ~/.bash_history
tac ~/.bash_history | awk '!a[$0]++' | tac > t; mv t ~/.bash_history
Lri
fuente
Para ser explícito, ¿entiendo bien que ha mostrado dos soluciones (espléndidas) aquí, y que un usuario solo necesita ejecutar una de ellas? ¿El rubí o el Bash?
Jonathan Hartley
3

Esta es una publicación antigua, pero un problema perpetuo para los usuarios que desean tener múltiples terminales abiertas y tener el historial sincronizado entre ventanas, pero no duplicado.

Mi solución en .bashrc:

shopt -s histappend
export HISTCONTROL=ignoreboth:erasedups
export PROMPT_COMMAND="history -n; history -w; history -c; history -r"
tac "$HISTFILE" | awk '!x[$0]++' > /tmp/tmpfile  &&
                tac /tmp/tmpfile > "$HISTFILE"
rm /tmp/tmpfile
  • La opción histappend agrega el historial del búfer al final del archivo histórico ($ HISTFILE)
  • ignoreboth y borrados evitan que se guarden entradas duplicadas en el $ HISTFILE
  • El comando prompt actualiza el caché del historial
    • history -n lee todas las líneas de $ HISTFILE que pueden haber ocurrido en un terminal diferente desde el último retorno de carro
    • history -w escribe el búfer actualizado en $ HISTFILE
    • history -c limpia el búfer para que no se produzca duplicación
    • history -r vuelve a leer el $ HISTFILE, agregando al búfer ahora en blanco
  • El script awk almacena la primera aparición de cada línea que encuentra. taclo revierte y luego lo revierte para que se pueda guardar con los comandos más recientes aún más recientes en el historial
  • rm el archivo / tmp

Cada vez que abre un nuevo shell, el historial borra todos los duplicados, y cada vez que presiona la Entertecla en una ventana de shell / terminal diferente, actualiza este historial del archivo.

Rana sonriente
fuente
Aquí hay una excelente explicación de esto en los comentarios
sonriente rana
Si "ignoreboth y borrados evitan que los duplicados se guarden", entonces ¿por qué también necesita hacer el comando "awk" para eliminar los duplicados del archivo? ¿Es porque "ignorar ambas cosas y las borradas" solo impiden que se salven los engaños consecutivos ? Lamento ser pedante, solo estoy tratando de entender.
Jonathan Hartley
1
erasedups solo borra duplicados consecutivos. Y tiene razón en que el comando awk duplica el comando erasedupes haciéndolo superfluo.
smilingfrog
Gracias, eso me deja claro lo que está pasando.
Jonathan Hartley
0

Grabar unívocamente cada nuevo comando es complicado. Primero necesita agregar ~/.profileo similar:

HISTCONTROL=erasedups
PROMPT_COMMAND='history -w'

Entonces necesitas agregar a ~/.bash_logout:

history -a
history -w
Steven Penny
fuente
¿Puede ayudarme a entender por qué, al cerrar sesión, necesita agregar un historial no escrito al archivo de historial antes de volver a escribir todo el archivo de historial? ¿No puedes escribir todo el archivo sin el 'agregar'?
Jonathan Hartley