Básicamente, lo que quiero hacer es comparar dos archivos por línea por columna 2. ¿Cómo podría lograr esto?
Archivo_1.txt:
User1 US
User2 US
User3 US
Archivo_2.txt:
User1 US
User2 US
User3 NG
Archivo de salida:
User3 has changed
Básicamente, lo que quiero hacer es comparar dos archivos por línea por columna 2. ¿Cómo podría lograr esto?
Archivo_1.txt:
User1 US
User2 US
User3 US
Archivo_2.txt:
User1 US
User2 US
User3 NG
Archivo de salida:
User3 has changed
Respuestas:
Mira el diff
comando. Es una buena herramienta, y puede leer todo al escribir man diff
en su terminal.
El comando que querrás hacer es el diff File_1.txt File_2.txt
que generará la diferencia entre los dos y debería verse así:
Una nota rápida sobre la lectura del resultado del tercer comando: las 'flechas' ( <
y >
) se refieren a cuál es el valor de la línea en el archivo izquierdo ( <
) frente al archivo derecho ( >
), siendo el archivo izquierdo el que ingresó primero en la línea de comando, en este casoFile_1.txt
Además, es posible que observe que el cuarto comando es que diff ... | tee Output_File
canaliza los resultados de diff
a tee
, que luego coloca esa salida en un archivo, para que pueda guardarlo para más adelante si no desea verlo todo en la consola en ese momento.
diff file1 file2 -s
. Aquí hay un ejemplo: imgur.com/ShrQx9x
O puedes usar Meld Diff
Meld te ayuda a comparar archivos, directorios y proyectos controlados por versiones. Proporciona una comparación de dos y tres vías de archivos y directorios, y tiene soporte para muchos sistemas de control de versiones populares.
Instalar ejecutando:
sudo apt-get install meld
Su ejemplo:
Comparar directorio:
Ejemplo con texto completo:
dos
y el segundo en unix
.
FWIW, me gusta bastante lo que obtengo con la salida de lado a lado de diff
diff -y -W 120 File_1.txt File_2.txt
daría algo como:
User1 US User1 US
User2 US User2 US
User3 US | User3 NG
Puedes usar el comando cmp
:
cmp -b "File_1.txt" "File_2.txt"
la salida sería
a b differ: byte 25, line 3 is 125 U 116 N
cmp
es mucho más rápido que diff
si todo lo que quieres es el código de retorno.
Literalmente apegado a la pregunta (archivo1, archivo2, archivo de salida con el mensaje "ha cambiado") el script a continuación funciona.
Copie el script en un archivo vacío, guárdelo como compare.py
, hágalo ejecutable, ejecútelo con el comando:
/path/to/compare.py <file1> <file2> <outputfile>
La secuencia de comandos:
#!/usr/bin/env python
import sys
file1 = sys.argv[1]; file2 = sys.argv[2]; outfile = sys.argv[3]
def readfile(file):
with open(file) as compare:
return [item.replace("\n", "").split(" ") for item in compare.readlines()]
data1 = readfile(file1); data2 = readfile(file2)
mismatch = [item[0] for item in data1 if not item in data2]
with open(outfile, "wt") as out:
for line in mismatch:
out.write(line+" has changed"+"\n")
Con unas pocas líneas adicionales, puede hacer que se imprima en un archivo de salida o en el terminal, dependiendo de si el archivo de salida está definido:
Para imprimir en un archivo:
/path/to/compare.py <file1> <file2> <outputfile>
Para imprimir en la ventana de terminal:
/path/to/compare.py <file1> <file2>
La secuencia de comandos:
#!/usr/bin/env python
import sys
file1 = sys.argv[1]; file2 = sys.argv[2]
try:
outfile = sys.argv[3]
except IndexError:
outfile = None
def readfile(file):
with open(file) as compare:
return [item.replace("\n", "").split(" ") for item in compare.readlines()]
data1 = readfile(file1); data2 = readfile(file2)
mismatch = [item[0] for item in data1 if not item in data2]
if outfile != None:
with open(outfile, "wt") as out:
for line in mismatch:
out.write(line+" has changed"+"\n")
else:
for line in mismatch:
print line+" has changed"
Una manera fácil es usarla colordiff
, que se comporta como diff
pero colorea su salida. Esto es muy útil para leer diferencias. Usando tu ejemplo,
$ colordiff -u File_1.txt File_2.txt
--- File_1.txt 2016-12-24 17:59:17.409490554 -0500
+++ File_2.txt 2016-12-24 18:00:06.666719659 -0500
@@ -1,3 +1,3 @@
User1 US
User2 US
-User3 US
+User3 NG
donde la u
opción da una diferencia unificada. Así es como se ve la diferencia coloreada:
Instalar colordiff
ejecutando sudo apt-get install colordiff
.
Si no hay necesidad de saber qué partes de los archivos difieren, puede usar la suma de comprobación del archivo. Hay muchas formas de hacerlo, usando md5sum
o sha256sum
. Básicamente, cada uno de ellos genera una cadena en la que un archivo contiene hash. Si los dos archivos son iguales, su hash también será el mismo. Esto se usa a menudo cuando descarga software, como imágenes iso de instalación de Ubuntu. A menudo se usan para verificar la integridad de un contenido descargado.
Considere la secuencia de comandos a continuación, donde puede dar dos archivos como argumentos, y el archivo le dirá si son iguales o no.
#!/bin/bash
# Check if both files exist
if ! [ -e "$1" ];
then
printf "%s doesn't exist\n" "$1"
exit 2
elif ! [ -e "$2" ]
then
printf "%s doesn't exist\n" "$2"
exit 2
fi
# Get checksums of eithe file
file1_sha=$( sha256sum "$1" | awk '{print $1}')
file2_sha=$( sha256sum "$2" | awk '{print $1}')
# Compare the checksums
if [ "x$file1_sha" = "x$file2_sha" ]
then
printf "Files %s and %s are the same\n" "$1" "$2"
exit 0
else
printf "Files %s and %s are different\n" "$1" "$2"
exit 1
fi
Ejecución de muestra:
$ ./compare_files.sh /etc/passwd ./passwd_copy.txt
Files /etc/passwd and ./passwd_copy.txt are the same
$ echo $?
0
$ ./compare_files.sh /etc/passwd /etc/default/grub
Files /etc/passwd and /etc/default/grub are different
$ echo $?
1
Además, hay un comm
comando, que compara dos archivos ordenados, y da salida en 3 columnas: columna 1 para elementos únicos para el archivo # 1, columna 2 para elementos únicos para el archivo # 2 y columna 3 para elementos presentes en ambos archivos.
Para suprimir cualquiera de las columnas, puede usar los modificadores -1, -2 y -3. El uso de -3 mostrará las líneas que difieren.
A continuación puede ver la captura de pantalla del comando en acción.
Solo hay un requisito: los archivos deben ordenarse para que se puedan comparar correctamente. sort
El comando se puede utilizar para ese propósito. A continuación se muestra otra captura de pantalla, donde los archivos se ordenan y luego se comparan. Las líneas que comienzan en la campana izquierda son solo para File_1, las líneas que comienzan en la columna 2 pertenecen solo a File_2
Instalar git y usar
$ git diff filename1 filename2
Y obtendrá la salida en un bonito formato de color
Instalación de Git
$ apt-get update
$ apt-get install git-core
Compara pares de nombre / valor en 2 archivos en el formato name value\n
. Escribe el name
para Output_file
si ha cambiado. Requiere bash v4 + para matrices asociativas .
$ ./colcmp.sh File_1.txt File_2.txt
User3 changed from 'US' to 'NG'
no change: User1,User2
$ cat Output_File
User3 has changed
cmp -s "$1" "$2"
case "$?" in
0)
echo "" > Output_File
echo "files are identical"
;;
1)
echo "" > Output_File
cp "$1" ~/.colcmp.array1.tmp.sh
sed -i -E "s/([^A-Za-z0-9 ])/\\\\\\1/g" ~/.colcmp.array1.tmp.sh
sed -i -E "s/^(.*)$/#\\1/" ~/.colcmp.array1.tmp.sh
sed -i -E "s/^#\\s*(\\S+)\\s+(\\S.*?)\\s*\$/A1\\[\\1\\]=\"\\2\"/" ~/.colcmp.array1.tmp.sh
chmod 755 ~/.colcmp.array1.tmp.sh
declare -A A1
source ~/.colcmp.array1.tmp.sh
cp "$2" ~/.colcmp.array2.tmp.sh
sed -i -E "s/([^A-Za-z0-9 ])/\\\\\\1/g" ~/.colcmp.array2.tmp.sh
sed -i -E "s/^(.*)$/#\\1/" ~/.colcmp.array2.tmp.sh
sed -i -E "s/^#\\s*(\\S+)\\s+(\\S.*?)\\s*\$/A2\\[\\1\\]=\"\\2\"/" ~/.colcmp.array2.tmp.sh
chmod 755 ~/.colcmp.array2.tmp.sh
declare -A A2
source ~/.colcmp.array2.tmp.sh
USERSWHODIDNOTCHANGE=
for i in "${!A1[@]}"; do
if [ "${A2[$i]+x}" = "" ]; then
echo "$i was removed"
echo "$i has changed" > Output_File
fi
done
for i in "${!A2[@]}"; do
if [ "${A1[$i]+x}" = "" ]; then
echo "$i was added as '${A2[$i]}'"
echo "$i has changed" > Output_File
elif [ "${A1[$i]}" != "${A2[$i]}" ]; then
echo "$i changed from '${A1[$i]}' to '${A2[$i]}'"
echo "$i has changed" > Output_File
else
if [ x$USERSWHODIDNOTCHANGE != x ]; then
USERSWHODIDNOTCHANGE=",$USERSWHODIDNOTCHANGE"
fi
USERSWHODIDNOTCHANGE="$i$USERSWHODIDNOTCHANGE"
fi
done
if [ x$USERSWHODIDNOTCHANGE != x ]; then
echo "no change: $USERSWHODIDNOTCHANGE"
fi
;;
*)
echo "error: file not found, access denied, etc..."
echo "usage: ./colcmp.sh File_1.txt File_2.txt"
;;
esac
Desglose del código y lo que significa, a mi entender. Agradezco las ediciones y sugerencias.
cmp -s "$1" "$2"
case "$?" in
0)
# match
;;
1)
# compare
;;
*)
# error
;;
esac
cmp establecerá el valor de $? como sigue :
Decidí usar un caso ... esa declaración para evaluar $? porque el valor de $? cambia después de cada comando, incluida la prueba ([).
Alternativamente, ¿podría haber usado una variable para mantener el valor de $? :
cmp -s "$1" "$2"
CMPRESULT=$?
if [ $CMPRESULT -eq 0 ]; then
# match
elif [ $CMPRESULT -eq 1 ]; then
# compare
else
# error
fi
Arriba hace lo mismo que la declaración del caso. IDK que me gusta más.
echo "" > Output_File
Arriba borra el archivo de salida, por lo que si ningún usuario ha cambiado, el archivo de salida estará vacío.
Hago esto dentro de las declaraciones de caso para que el Output_file permanezca sin cambios en caso de error.
cp "$1" ~/.colcmp.arrays.tmp.sh
Arriba copia File_1.txt al directorio de inicio del usuario actual.
Por ejemplo, si el usuario actual es john, lo anterior sería lo mismo que cp "File_1.txt" /home/john/.colcmp.arrays.tmp.sh
Básicamente, soy paranoico. Sé que estos caracteres podrían tener un significado especial o ejecutar un programa externo cuando se ejecutan en un script como parte de la asignación de variables:
Lo que no sé es cuánto no sé sobre bash. No sé qué otros personajes podrían tener un significado especial, pero quiero escapar de todos con una barra invertida:
sed -i -E "s/([^A-Za-z0-9 ])/\\\\\\1/g" ~/.colcmp.array1.tmp.sh
sed puede hacer mucho más que la coincidencia de patrones de expresión regular . El patrón de script "s / (find) / (replace) /" realiza específicamente la coincidencia del patrón.
"s / (buscar) / (reemplazar) / (modificadores)"
en inglés: capturar cualquier puntuación o carácter especial como caputure group 1 (\\ 1)
en inglés: prefija todos los caracteres especiales con una barra invertida
en inglés: si se encuentra más de una coincidencia en la misma línea, reemplácelas todas
sed -i -E "s/^(.*)$/#\\1/" ~/.colcmp.arrays.tmp.sh
El anterior usa una expresión regular para prefijar cada línea de ~ / .colcmp.arrays.tmp.sh con un carácter de comentario bash ( # ). Hago esto porque más tarde tengo la intención de ejecutar ~ / .colcmp.arrays.tmp.sh usando el comando de origen y porque no sé con seguridad el formato completo de File_1.txt .
No quiero ejecutar accidentalmente código arbitrario. No creo que nadie lo haga.
"s / (buscar) / (reemplazar) /"
en inglés: captura cada línea como caputure group 1 (\\ 1)
en inglés: reemplace cada línea con un símbolo de libra seguido de la línea que fue reemplazada
sed -i -E "s/^#\\s*(\\S+)\\s+(\\S.*?)\\s*\$/A1\\[\\1\\]=\"\\2\"/" ~/.colcmp.arrays.tmp.sh
Arriba está el núcleo de este script.
#User1 US
A1[User1]="US"
A2[User1]="US"
(para el segundo archivo)"s / (buscar) / (reemplazar) /"
en inglés:
captura el resto de la línea como grupo de captura 2
(reemplazar) = A1 \\ [\\ 1 \\] = \ "\\ 2 \"
A1[
para iniciar la asignación de matriz en una matriz llamadaA1
]="
]
= asignación de matriz cerrada, por ejemplo, A1[
Usuario1 ]="
US"
=
= operador de asignación, por ejemplo, variable = valor"
= valor de cotización para capturar espacios ... aunque ahora que lo pienso, hubiera sido más fácil dejar que el código anterior reduzca todo a los caracteres de espacio.en inglés: reemplace cada línea en el formato #name value
con un operador de asignación de matriz en el formatoA1[name]="value"
chmod 755 ~/.colcmp.arrays.tmp.sh
El anterior usa chmod para hacer que el archivo de script de matriz sea ejecutable.
No estoy seguro si esto es necesario.
declare -A A1
La A mayúscula indica que las variables declaradas serán matrices asociativas .
Es por eso que el script requiere bash v4 o superior.
source ~/.colcmp.arrays.tmp.sh
Ya lo tenemos:
User value
a líneas de A1[User]="value"
,Por encima de que la fuente de la secuencia de comandos para ejecutar en el shell actual. Hacemos esto para poder mantener los valores variables que establece el script. Si ejecuta el script directamente, genera un nuevo shell, y los valores de las variables se pierden cuando el nuevo shell sale, o al menos eso entiendo.
cp "$2" ~/.colcmp.array2.tmp.sh
sed -i -E "s/([^A-Za-z0-9 ])/\\\\\\1/g" ~/.colcmp.array2.tmp.sh
sed -i -E "s/^(.*)$/#\\1/" ~/.colcmp.array2.tmp.sh
sed -i -E "s/^#\\s*(\\S+)\\s+(\\S.*?)\\s*\$/A2\\[\\1\\]=\"\\2\"/" ~/.colcmp.array2.tmp.sh
chmod 755 ~/.colcmp.array2.tmp.sh
declare -A A2
source ~/.colcmp.array2.tmp.sh
Hacemos lo mismo por $ 1 y A1 que por $ 2 y A2 . Realmente debería ser una función. Creo que en este momento este script es lo suficientemente confuso y funciona, así que no lo arreglaré.
for i in "${!A1[@]}"; do
# check for users removed
done
Bucles anteriores a través de claves de matriz asociativas
if [ "${A2[$i]+x}" = "" ]; then
Lo anterior utiliza la sustitución de variables para detectar la diferencia entre un valor no establecido frente a una variable que se ha establecido explícitamente en una cadena de longitud cero.
Aparentemente, hay muchas maneras de ver si se ha establecido una variable . Elegí el que tenía más votos.
echo "$i has changed" > Output_File
Arriba agrega el usuario $ i al Output_File
USERSWHODIDNOTCHANGE=
Arriba borra una variable para que podamos hacer un seguimiento de los usuarios que no cambiaron.
for i in "${!A2[@]}"; do
# detect users added, changed and not changed
done
Bucles anteriores a través de claves de matriz asociativas
if ! [ "${A1[$i]+x}" != "" ]; then
El anterior usa la sustitución de variables para ver si se ha establecido una variable .
echo "$i was added as '${A2[$i]}'"
Como $ i es la clave de matriz (nombre de usuario) $ A2 [$ i] debería devolver el valor asociado con el usuario actual de File_2.txt .
Por ejemplo, si $ i es Usuario1 , lo anterior se lee como $ {A2 [Usuario1]}
echo "$i has changed" > Output_File
Arriba agrega el usuario $ i al Output_File
elif [ "${A1[$i]}" != "${A2[$i]}" ]; then
Como $ i es la clave de matriz (nombre de usuario) $ A1 [$ i] debería devolver el valor asociado con el usuario actual de File_1.txt , y $ A2 [$ i] debería devolver el valor de File_2.txt .
Arriba compara los valores asociados para el usuario $ i de ambos archivos.
echo "$i has changed" > Output_File
Arriba agrega el usuario $ i al Output_File
if [ x$USERSWHODIDNOTCHANGE != x ]; then
USERSWHODIDNOTCHANGE=",$USERSWHODIDNOTCHANGE"
fi
USERSWHODIDNOTCHANGE="$i$USERSWHODIDNOTCHANGE"
Arriba crea una lista separada por comas de usuarios que no cambiaron. Tenga en cuenta que no hay espacios en la lista, de lo contrario, se deberá citar la siguiente comprobación.
if [ x$USERSWHODIDNOTCHANGE != x ]; then
echo "no change: $USERSWHODIDNOTCHANGE"
fi
Arriba se informa el valor de $ USERSWHODIDNOTCHANGE pero solo si hay un valor en $ USERSWHODIDNOTCHANGE . La forma en que esto está escrito, $ USERSWHODIDNOTCHANGE no puede contener espacios. Si necesita espacios, lo anterior podría reescribirse de la siguiente manera:
if [ "$USERSWHODIDNOTCHANGE" != "" ]; then
echo "no change: $USERSWHODIDNOTCHANGE"
fi
diff "File_1.txt" "File_2.txt"