En este post compartiré algunos scripts en [bash](https://es.wikipedia.org/wiki/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](https://es.wikipedia.org/wiki/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 ##

* [Programación](#para-programacion)
	- [Búsqueda de palabras o expresiones](#busqueda-de-palabras-o-expresiones)
* [Administración](#para-administracion)
	- [Comando less manteniendo colores](#comando-less-manteniendo-colores)
	- [Tamaño de carpetas](#tamano-de-carpetas)
	- [Digestos de archivos en el directorio actual](#digestos-de-archivos-en-el-directorio-actual)
	- [Archivos duplicados](#archivos-duplicados)

### 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:

    :::bash
    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:

	:::bash
	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`:

	:::bash
	# 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.

	:::bash
    #!/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:

	:::bash
	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.

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

[Más ejemplos](https://superuser.com/questions/36022/less-and-grep-getting-colored-results-when-using-a-pipe-from-grep-to-less)

#### Tamaño de carpetas

	:::bash
	# 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`:

	:::bash
	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:

    :::bash
    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 <--](https://notabug.org/strysg/duplicados.py/src/master/duplicados.py) si quisieras contribuir.

	:::python
    #!/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](/contacto), espero te haya servido.
