sitio personal de Rodrigo Garcia.

Utilidades bash 1


permalink.

Actualizado - 31 March 2019

En este post compartiré algunos scripts en bash, python y combinación de comandos que me han servido en distintas ocasiones como para escribir programas, estudiar, buscar, administrar sistemas, etc.

Los scripts bash requieren tener instalado bash que viene instalado para la mayoría de las distribuciones GNU/Linux, para las utilidades siguientes no he definido un orden pero aquí una tabla pequeña de contenidos:

Índice

Para programación

Búsqueda de palabras o expresiones

Los programas pueden tener muchos archivos distintos con muchas líneas de código. En varias ocasiones es necesario buscar una palabra o expresión en particular en todos los archivos de código fuente del programa y podemos usar:

grep -lir "palabra" .

Esta orden usa grep en modo recursivo -r para buscar en todos los ficheros de subdirectorios, -l en lugar de mostrar las líneas donde se encuentran ocurrencias solamente muestra el archivo donde se ha encontrado la "palabra". -i hace se ignore la diferencia entre mayúsculas y minúsculas, finalmente. le dice que busque en el directorio actual.

El comando anterior es muy útil, pero puede que necesitemos más detalles por ejemplo saber cuántas veces se encuentra la "palabra" en cada archivo para lo cual podemos usar los siguiente:

grep -irc "palabra" . | grep -E --color "[1-9]+[0-9]*$"

Que muestra el número de veces que se encuentra "palabra" en cada archivo gracias a la opción c. Luego de la tubería otro grep filtra una expresión regular (por usar -E), la expresión regular [1-9]+[0-9]*$ coincide con todas las cadenas que *terminen en al menos una combinación de números entre 1 y 9 seguido de cero o más combinaciones de números entre 0 y 9.

Se usa este filtro por que la opción -c del grep inicial también imprime 0 en caso de que no se encuentren ocurrencias en un archivo, pero nosotros sólo queremos que se muestren los archivos en donde se encuentre al menos una ocurrencia.

El script anterior también es muy útil y gracias a --color en la salida final se mantiene resaltado en colores el número de ocurrencias en un archivo. Sin embargo puede perfeccionarse, por ejemplo en un proyecto en python o node.js se tienen los directorios venv y node_modules con archivos de código fuente de las extensiones o programas que usa el programa principal, pero incluirlos en la búsqueda seguramente no es lo que buscamos. Para que se ignoren estos directorios usamos --exclude-dir:

# para python
grep -irc "palabra" --exclude-dir=venv . | grep -E --color "[1-9]+[0-9]*$"
# para node.js
grep -irc "palabra" --exclude-dir=node_modules . | grep -E --color "[1-9]+[0-9]*$"

Ahora para no tener que escribir todo el tiempo la larga combinación de arriba y tener mayor facilidad lo convertiremos en un útil script.

#!/bin/bash

uso()
{
    echo "Uso:"
    echo "    pgrep.sh [-e DIRECTORIO] EXPRESION"
    echo ""
    echo "Busca la EXPRESION en todos los ficheros dentro el directorio actual"
    echo "Si se ysa '-e' se excluye el DIRECTORIO en la búsqueda."
    exit 0
}
EXCLUDE=""
EXPR=""
if [ -z $1 ]
then
    uso
fi
if [ $1 == "-e" ] # excluir directorio
then
    if [ -z $2 ] 
    then
        uso
    fi
    EXCLUDE=$2
    EXPR=$3
else
    EXPR=$1
fi
# comando final de búsqueda
grep -rc "$EXPR" --exclude-dir="$EXCLUDE" . | grep -E --color "[1-9]+[0-9]*$"
exit 0

Podríamos incluso incluir el script anterior como global para usarlo en cualquier momento, para eso lo guardamos en una ruta dentro el PATH por ejemplo yo lo he guardado así /usr/bin/ogrep.sh luego le damos permisos de ejecución (chmod +x /usr/bin/ogrep.sh) y listo.

Para administración

Comando less manteniendo colores

A menudo se usa less para capturar la salida de uno o más comandos en una consola, al usar less los colores en el texto de salida se puede perder por ejemplo si se hace ls -l | less. Aquí un ejemplo para mantener los colores usando less:

ls -l --color | less -R

la opción -R de less hace que se interpreten caracteres en bruto en este caso códigos de colores que ls con la opción --color produce.

# mantiene los colores en resultados de buscar .html en un archivo
grep ".html$" archivo --color=always | less -R

Más ejemplos

Tamaño de carpetas

# Espacio que ocupa cada carpeta en el directorio actual
du -h --max-depth 1 .
# Espacio que ocupa el directorio actual
du -sh

Digestos de archivos en el directorio actual

Una manera simple para obtener sha1:

sha1sum *

Que obtiene el digesto sha1 de todos los archivos en el directorio actual aunque este también intenta obtener de los directorios, para solamente obtener de archivos se puede usar:

find . -maxdepth 1 -type f | tr "\n" " " | xargs sha1sum
# de la misma forma sin usar tr (mas eficiente)
find . -maxdepth 1 -type f -print0 | xargs --delimiter "\0" sha1sum

Donde . hace que se busque en el directorio actual, -maxdepth con una profundidad de 1 y de tipo archivo (-type f), luego se usa tr "\n" " " que reemplaza los saltos de línea por espacios en blanco para que xargs se los pase a sha1sum. La segunda forma es más eficiente al no tener que llamar a otro comando.

Archivos duplicados

El siguiente script permite buscar archivos duplicados dentro un directorio dado analizando los nombres de archivos y si el contenido. Esta escrito en python y puede ejecutarse con:

python3 duplicados.py

El script se puede mejorar o agregar funcionalidades --> aquí la última versión <-- si quisieras contribuir.

#!/usr/bin/python3
import hashlib
import os
import sys

# helpers
def getList(directory="."):
    ''' retorna una lista con los nombres de todos los archivos dentro el 
    directorio actual.
    * basado en https://stackoverflow.com/questions/120656/directory-tree-listing-in-python#120701
    '''
    files = []
    for dirname, dirnames, filenames in os.walk(directory):
        # for subdirname in dirnames:
        #     files.append(os.apth.join(dirname, subdirname))
        for filename in filenames:
            files.append(os.path.join(dirname,filename))
    return files

def digest(filename, algorithm='sha1'):
    ''' returns hexdigest of the given filename using the gibe algorithm '''
    with open(filename, 'r+b') as fil:
        if (algorithm == 'sha1'):
            return hashlib.sha1(fil.read()).hexdigest()
        elif (algorithm == 'sha224'):
            return hashlib.sha224(fil.read()).hexdigest()
        elif (algorithm == 'sha256'):
            return hashlib.sha256(fil.read()).hexdigest()
        elif (algorithm == 'sha384'):
            return hashlib.sha384(fil.read()).hexdigest()
        elif (algorithm == 'sha512'):
            return hashlib.sha512(fil.read()).hexdigest()
        elif (algorithm == 'md5'):
            return hashlib.md5(fil.read()).hexdigest()
        print ('Invalid algorithm')
        return ""

def digests(fileList, algorithm='sha1'):
    ''' Retorna un diccionario con los digestos calculados de la lista de
    archivos `fileList'.
    '''
    dict = {}
    for file in fileList:
        d = digest(file)
        if d in dict:
            # duplicado encontrado
            l = dict[d]
            l.append(file)
            dict[d] = l
        else:
            l = []
            l.append(file)
            dict[d] = l
    return dict

def use():
    print ('Obtiene un lista de archivos duplicados desde un directorio raíz')
    print ('Cada linea contiene los archivos que se ha detectado iguales')
    print ()
    print ('  python3 duplicados.py [DIR] [ALGORITMO]')
    print ()
    print (' - DIR: Directorio raíz donde realizar la búsqueda usa "." por defecto')
    print (' - ALGORITMO: Algoritmo para obtener digestos "sha1" por defecto')
    print ('              permitidos; md5,sha1,sha224,sha256,sha384,sha512')

# main
files = []
directory='.'
hashAlgorithm = "sha1"

if (len(sys.argv) > 1):
    if (sys.argv[1] != ''):
        if (sys.argv[1]=='-h' or sys.argv[1]=='--help'):
            use()
            exit(0)

        directory=sys.argv[1]
if (len(sys.argv) > 2):
    if (sys.argv[2] != ''):
        if (sys.argv[2]=='sha1' or sys.argv[2]=='md5' or sys.argv[2]=='sha224'
            or sys.argv[2]=='sha256' or sys.argv[2]=='sha512'):
            hashAlgorithm = sys.argv[2]
        else:
            use()
            exit(0)

files = getList(directory)
digests = digests(files, hashAlgorithm)
for digest, lista in digests.items():
    # verbose
    #print (digest, lista)

    if len(lista) > 1:
        s = ''
        for file in lista:
            s += file+' '
        print(s[:-1])
exit(0)

La idea del script anterior se puede explotar de muchas formas, al usarlo se obtiene la lista de archivos que se detectan con contenido igual y a partir de esa lista se puede por ejemplo eliminar los duplicados. Además para que parezcan que no se han eliminado se podría crear enlaces simbólicos y sólo mantener un archivo con contenido original por todos los demás repetidos.


Si tienes comentarios sobre lo mostrado escríbeme, espero te haya servido.