Categoría: Linux

  • La rueda del ratón recorre el historial de comandos en lugar de desplazar la terminal

    Hace poco me volvió a pasar que después de algún evento que no consigo identificar bien, la rueda del raton en lugar de desplazar la vista de la terminal empieza a recorrer su historial de comandos.

    Algunas aplicaciones de terminal tienen la capacidad de restaurar el comportamiento anterior, pero este no era el caso así que busqué por Internet y descubrí un comando del que nunca antes había oído hablar; tput. En concreto la llamada que logra restaurar el desplazamiento de la terminal es el siguiente:

    tput rmcup

  • Utilizando el comando ‘read’

    El comando read permite leer la entrada de texto del usuario y guardarlo en una variable de entorno. Además, puede separar la entrada en campos definiendo en la variable IFS (Input Field Separator) el separador.

    IP=$(ip a show dev eth0 | awk '/inet / { print $2}')
    IFS=/ read ip mask <<< "$IP"
    IFS=. read ip1 ip2 ip3 ip4 <<< "$ip"
    echo ${ip1}.${ip2}.${ip3}.${ip4}/${mask}
  • Recuperar la huella digital de una clave pública reconocida

    Cuando nos conectamos por primera vez a un servidor SSH, se nos muestra la huella digital de su clave pública para que tengamos la oportunidad de verificar que ese es el servidor al que realmente deseamos conectarnos y no otro que de alguna forma está impersonándolo se está haciendo pasar por aquél.

    Mi antiguo yo tenía la fea costumbre de dar por buena dicha huella digital, confiando en que la resolución DNS, el enrutamiento y otras cuantas cosas no estuvieran comprometidos. Hasta hoy, hoy me he hecho una simple pregunta, ¿cómo verifico que esa huella es la del servidor que ya conozco y tengo reconocido en otra cuenta? Cómo no, la respuesta estaba en StackExchange – superuser.

    % ssh-keygen -l -f ~/.ssh/known_hosts
    2048 c2:e7:c0:9f:cd:c8:54:88:ac:b3:6b:a6:51:73:2b:e3 mach1,192.168.1.3 (RSA) 2048 a2:5e:8c:4e:2e:be:be:eb:23:12:5e:fe:6c:4b:23:dd mach2,192.168.1.1 (RSA) 1024 ae:5f:bc:e3:33:c3:dd:45:1e:18:1a:46:d1:d6:d2:39 mach3,192.168.1.6 (RSA) ... ...

    Y para nuestro servidor en concreto pues:

    % ssh-keygen -l -f ~/.ssh/known_hosts -F mach1
    2048 c2:e7:c0:9f:cd:c8:54:88:ac:b3:6b:a6:51:73:2b:e3 mach1 (RSA)
    
  • Uso de la salida de un comando como contenido de un fichero

    En ocasiones es conveniente utilizar la salida de un comando como si fuera el contenido de un fichero que se quiere pasar a otro comando como argumento.

    El caso más común con el que yo me encuentro es comparar la salida de dos comandos. Por ejemplo comparar las diferencias entre revisiones de dos ficheros.

    git diff mainline -- file1
    git diff mainline -- file2

    Puesto que diff espera como argumentos nombres de ficheros la solución más inmediata sería enviar la salida de cada comando a un fichero distinto y comparar ambos ficheros. Sin embargo, esto no queda muy bien que digamos.

    Afortunadamente los desarrolladores de Bash y Zsh (entre otros intérpretes de comandos) tampoco estaban contentos con esa chapuza, así que implementaron la capacidad llamada Sustitución de Proceso.

    La sustitución de procesos permite que la salida (o la entrada) sea referida como un nombre de fichero. Así, la ejecución de diff sobre la salida de los dos comandos anteriores se expresaría de la siguiente forma:

    diff <(git diff mainline -- file1) <(git diff mainline -- file2)

    Si lo que se quiere es utilizar la entrada de un comando como fichero se expresaría del siguiente modo:

    date > >(cat)

    Sí, ya sé que es un ejemplo un poco cutre, pero se entiende.

  • Configuración de tmux

    Contenido de mi fichero ~/.tmux.conf:

    set-window-option -g mouse on
    set -g terminal-overrides 'xterm*:smcup@:rmcup@'

    Eso sirve para desplazar el buffer de la terminal con la rueda del ratón, en lugar de cambiar la entrada de texto desde la historia de comandos.

  • Indicador (prompt) para el intérprete de comandos Zsh

    Configuración del prompt Zsh para mostrar ficheros con cambios o no registrados en los directorios parte de un repositorio git.

    Despues del exito de Indicadores (prompt) para el intérprete de comandos Bash llega la version para Zsh.

    Definición en ~/.zshrc:

     # Git support for prompt
    autoload -Uz vcs_info
    precmd_vcs_info() { vcs_info }
    precmd_functions+=( precmd_vcs_info )
    setopt prompt_subst

    export PROMPT="
    %{$fg[white]%}(%D{%Y-%m-%d} %*) <%(?.%?.%F{red}%?%f)> [%~] $program %{$fg[default]%}\$vcs_info_msg_0_
    %{$fg[cyan]%}%m %#%{$fg[default]%} "

    Resultado:

    (2019-12-03 13:59:19) <130> [~/git/repositorio] (git)-[RamaGit]-
    MyHostname %

    Aunque el prompt no esta mal se nota que el presupuesto no es el mismo que el de la primera version, de ahi que el resultado no se vea igual de bien que en la version para Bash.

    Ya se sabe, segundas partes nunca fueron buenas.

    Finalmente se encontró el presupuesto para mejorar los efectos especiales y este es el remake:

    export PATH=$HOME/bin:$PATH

    export CLICOLOR=1

    # Git support for prompt
    # zstyle ':vcs_info:git*' formats "%{$fg[grey]%}%s %{$reset_color%}%r/%S%{$fg[grey]%} %{$fg[blue]%}%b%{$reset_color%}%m%u%c%{$reset_color%} "
    zstyle ':vcs_info:*' enable git
    zstyle ':vcs_info:*' check-for-changes true
    zstyle ':vcs_info:*' unstagedstr '!'
    zstyle ':vcs_info:*' stagedstr '+'
    zstyle ':vcs_info:git*' formats "%F{blue}%r/%S%} %F{yellow}%b%f%m%F{red}%u%f%F{green}%c%f"
    autoload -Uz vcs_info
    precmd_vcs_info() { vcs_info }
    precmd_functions+=( precmd_vcs_info )
    setopt prompt_subst

    export PROMPT="
    %F{green}%D{%Y-%m-%d} %D{%H:%M:%S}%f [%F{magenta}%~%f] \$vcs_info_msg_0_
    %F{cyan]%}%n%f@%F{blue}%m%f%} %(?..<%K{red}%F{yellow}%?%f%k> )%# "

    export RPROMPT=

    Y este el resultado (aprox):

    2022-08-31 12:32:21 [~/workspaces/proyecto] repositorio_git/. RamaGit
    user@nombre_equipo % error
    zsh: command not found: error

    2022-08-31 12:32:24 [~/workspaces/proyecto] repositorio_git/. RamaGit
    user@nombre_equipo <127> %

    Por último, además de mostrar la existencia de cambios presentados o no (traducción libre de staged) a git, añado un bloque para mostrar también la presencia de ficheros no registrados por git (untracked).

    export CLICOLOR=1

    # Git support for prompt
    zstyle ':vcs_info:*' enable git
    zstyle ':vcs_info:*' check-for-changes true
    zstyle ':vcs_info:*' unstagedstr '!'
    zstyle ':vcs_info:*' stagedstr '+'
    zstyle ':vcs_info:git*' formats "%F{blue}%r/%S%} %F{yellow}%b%f%m%F{red}%u%f%F{green}%c%f"
    autoload -Uz vcs_info
    precmd_vcs_info() { vcs_info }
    precmd_functions+=( precmd_vcs_info )
    setopt prompt_subst

    ### Display the existence of files not yet known to VCS

    ### git: Show marker (*) if there are untracked files in repository
    # Make sure you have added staged to your 'formats': %c
    zstyle ':vcs_info:git*+set-message:*' hooks git-untracked

    +vi-git-untracked(){
    if [[ $(git rev-parse --is-inside-work-tree 2> /dev/null) == 'true' ]] && \
    git status --porcelain | grep '??' &> /dev/null ; then
    # This will show the marker if there are any untracked files in repo.
    # If instead you want to show the marker only if there are untracked
    # files in $PWD, use:
    #[[ -n $(git ls-files --others --exclude-standard) ]] ; then
    hook_com[staged]+='%K{red]%}%F{black}*%f%k'
    fi
    }

    # export PROMPT="
    %F{green}%D{%Y-%m-%d} %D{%H:%M:%S}%f [%F{magenta}%~%f] \$vcs_info_msg_0_
    %F{cyan]%}%n%f@%F{blue}%m%f%} %(?..<%K{red}%F{yellow}%?%f%k> )%# "
    export PROMPT="
    %F{green}%D{%Y-%m-%d} %D{%H:%M:%S}%f \$vcs_info_msg_0_%F{cyan]%}%n%f [%F%F{magenta}%~%f%f%}] %(?..<%K{red}%F{yellow}%?%f%k> )%# "

    export RPROMPT=
  • Eliminar doble salto de línea de un fichero

    Varias veces había intentado utilizar sed para eliminar el doble salto de línea que aparece en algunos ficheros, normalmente en las copias de la configuración de algunos switches. Nunca lo había conseguido y no entendía muy bien por qué. Creía que sed no entendía la especificación del caracter salto de línea como \n, claro que tampoco funcionaba \\n ni \\\n.

    Cuando ví esta solución para sustituir saltos de línea por espacios entendí creí entender, como explicaré más adelante, que el problema es que sed va analizando línea por línea, eliminando el fin de línea de cada línea antes de realizar la búsqueda de patrones, por lo que, naturalmente, nunca encuentra dos saltos de línea seguidos en la misma línea.

    La solución ofrecida para reemplazar saltos de línea por espacios y la traducción de su explicación son las siguientes:

    sed :a;N;$!ba;s/\n/ /g

    :a crea una etiqueta, lo que nos permite a continuación añadir una nueva línea al espacio de reconocimiento de patrones con N. $!ba indica que no se aplique esta regla en la última línea, para dejar el salto de línea al final del fichero.

    Al modificarlo para reemplazar un doble salto de línea por uno probé lo siguiente con éxito:

    sed :a;N;$!ba;s/\n\n/\n/g

    Algo no cuadraba, pues según leí antes, sed eliminaba el salto de línea al final de cada línea, pero supuse que N, además de añadir una línea al espacio de búsqueda de patrones, evitaba la eliminación del salto de línea.

    Queriendo jugar un poco y aportar mi granito de arena a la solución, pensé que en este caso no era necesario hacer una excepción con la última línea, debería funcionar igualmente aunque sed intentase aplicar sin éxito la sustitución en la última línea y probé así:

    sed :a;N;s/\n\n/\n/g

    Pero así no hace nada de nada. ¿Cómo? ¿perdona?, ¿la última parte no era únicamente para no aplicar la regla en la última línea? Pues no. Así que tuve que continuar leyendo en busca de una respuesta mejor que la aceptada y más votada, y así llegué a una respuesta, que hacía referencia a las FAQ de sed. Sin embargo, el fragmento incluido tampoco aclaraba mis dudas.

    Así que seguí leyendo y casi al final del hilo, donde casi nunca hay buenas respuestas, encontré esta solución:

    sed -i "s/\\\\\n//g" filename

    De la que se obtenía para mi caso la siguiente, que funcionaba like a charm.

    sed 's/\\\\\n\\\\\n/\\\\\n/g'

    Quedaba claro que era mentira que sed eliminara los saltos de línea, así que no quedaba más remedio que seguir profundizando un poco, no para resolver el problema que ya estaba resuelto, sino porque no aguantaba más mi incultura en lo relacionado con sed.

    Explorando un poco en las FAQ de sed encontré una serie de scripts de una línea, entre los que había dos que hacían algo parecido a lo que quería:

       # Delete all CONSECUTIVE blank lines from file except the first.
       # This method also deletes all blank lines from top and end of file.
       # (emulates "cat -s")
       sed '/./,/^$/!d' file       # this allows 0 blanks at top, 1 at EOF
       sed '/^$/N;/\n$/D' file     # this allows 1 blank at top, 0 at EOF

    Otros scripts más que hacía lo mismo, pero no eran como los que había visto antes, pero ‘/./,/^$/.’, ¿esto que es?, no pararé hasta entender la sintaxis y semántica de todas estas soluciones, lo que finalmente, viendo algunos ejemplos más, logré.

    '/./,/^$/!d'

    La interpretación es la siguiente. /./,/^$/ especifica un rango sobre el que aplicar un comando, concretamente el rango entre ., es decir cualquier caracter, y ^$, es decir, una línea vacía. El ! a continuación niega la condición y la d elimina la coincidencia de la salida. Es decir, elimina los saltos de línea que no están precedidos por otro caracter.

    Esto me llevó a esta sencilla y elegante solución con la que termino ya:

    sed '/^$/d'
    

    Es decir, borra las líneas vacías.

    P.D. Reconozco que las diversas partes de este artículo son un poco incoherentes/inconexas/mejorables, pero tal como está ahora mismo sirve ya de ayuda para saber por donde tirar.

  • Indicadores (prompt) para el intérprete de comandos Bash

    Estos son mís «indicadores del intérprete de comandos» (o como se pueda traducir prompt) utilizados más habitualmente:

    Para mi usuario sin privilegios:

    0(0)[09:04]usuario@host[~]$

    Definición en ~/.bashrc:

    export PS1='\[\033[38;5;169m\]$?\[\033[38;5;247m\](\j)\[\033[38;5;6m\][\A]\[\033[0m\]\[\033[38;5;141m\]\u\[\033[35m\]@\[\033[1;32m\]\h\[\033[34m\][\w]\[\033[0m\]\$ '

    Para root:

    0(0)[09:05]root@host[~]$

    Definición en ~/.bashrc:

    export PS1='\[\033[38;5;169m\]$?\[\033[38;5;247m\](\j)\[\033[38;5;6m\][\A]\[\033[0m\]\[\033[48;5;196;1;32m\]\u\[\033[0m\]\[\033[38;5;141m\]@\[\033[1;32m\]\h\[\033[34m\][\w]\[\033[0m\]\$ '

    Ambos indicadores tienen los mismos siguientes campos:

    1. En magenta el código de resultado devuelto por el último comando ejecutado.
    2. En gris entre paréntesis el número de trabajos en segundo plano asociados al intérprete de comandos.
    3. En celeste entre corchetes la hora actual del sistema.
    4. En morado, o amarillo sobre rojo para root, el nombre del usuario.
    5. En verde el nombre del sistema.
  • Cambiar el TTL de paquetes IP ya enviados por la aplicación

    Cada paquete IP tiene en su cabecera un campo llamado TTL (Time To Live) destinado a evitar que el paquete quede eternamente circulando por la red en caso de algún problema de enrutamiento. Aunque el significado de sus siglas haga referencia al tiempo de vida, en realidad el valor del campo TTL representa el número máximo de saltos que puede dicho paquete realizar entre distintas redes.

    Para que esto se cumpla, cada vez que el paquete es enrutado de una red a otra, se decrementa el valor TTL del paquete. Si al pasar de una red a otra el campo TTL alcanza el valor 0 entonces el paquete se descarta sin enviarse a la siguiente red IP.

    Cada sistema operativo da un valor por defecto de TTL distinto a los paquetes unicast (con dirección IP de destino de un equipo concreto) que suele ser suficiente para alcanzar cualquier otro equipo del mundo a través de Internet (en Linux este valor es 64). Sin embargo, para los paquetes multicast (con dirección IP de destino de un grupo, no de ningún equipo concreto) el valor por defecto es 1, de modo que los paquetes multicast, por defecto, no puedan ser reenviados a otras redes distintas de aquella en la que se originaron.

    Afortunadamente, las aplicaciones pueden indicar que desean aplicar un TTL distinto a los paquetes multicast enviados por cierto socket. Según la norma POSIX se hace de la siguiente forma:

    unsigned char ttl = 16;
    setsockopt(socket, IPPROTOIP, IP_MULTICAST_TTL, &ttl, sizeof(ttl));

    También se puede establecer el TTL de forma genérica para todos los paquetes enviados utilizando la opción IP_TTL, en lugar de IP_MULTICAST_TTL.

     

    Bueno vale, pero el título dice que los paquetes ya están enviados por la aplicación. Es decir, no controlo o no puedo modificar la aplicación, así que, ¿qué se puede hacer si no se puede modificar cierta aplicación y se necesita cambiar el TTL de los paquetes que envía?

    socat

    Una solución obvia y bastante sencilla consiste en desplegar una aplicación que reciba dichos paquetes y reenvíe su contenido estableciendo el TTL como se desee. Esto se puede hacer de forma bastante sencilla con la aplicación socat. El siguiente ejemplo muestra como utilizarla para recibir unos paquetes multicast y reenviarlos con TTL 16 a otra dirección multicast:

    socat UDP4-RECV:$ORIGINAL_DEST_PORT,bind=$ORIGINAL_DESTINATION_IP,ip-add-membership=$ORIGINAL_DESTINATION_IP:$INGRESS_INTERFACE,reuseaddr UDP4-SENDTO:$NEW_DESTINATION_IP:$NEW_DESTINATION_PORT,ip-multicast-ttl=16,bind=$EGRESS_SOURCE_IP

    Esta orden abrirá un socket UDP de escucha en el puerto $ORIGINAL_DEST_PORT, asociado al interfaz (o interfaces) que correspondan a la dirección IP multicast $ORIGINAL_DESTINATION_IP y realizando la suscripción IGMP a la dirección de grupo multicast $ORIGINAL_DESTINATION_IP por el interfaz especificado por $INGRESS_INTERFACE. Los paquetes que reciba los enviará a la dirección IP $NEW_DESTINATION_IP y al puerto $NEW_DESTINATION_PORT con TTL 16 utilizando como dirección IP de origen $EGRESS_SOURCE_IP.

    iptables

    Otra solución mucho más versátil consiste en utilizar IPTABLES para modificar los paquetes mientras atraviesan la cadena de salida OUTPUT antes de que el sistema entregue el paquete al interfaz de red.

    La modificación del TTL solo se puede realizar en la tabla mangle, por lo que el comando IPTABLES para realizar este cambio sería el siguiente:

    iptables -t mangle -A OUTPUT -d $ORIGINAL_DESTINATION_IP -j TTL --ttl-set 16