Divers astuces en Shell

Posted on 2014-09-07 in Trucs et astuces Last modified on: 2015-02-20

Liste de commandes pour des actions spécifiques. Pas/peu de détails/explications, prérequis de shell supposés connus. Si une commande est donnée pour bash uniquement, cela signifie qu’elle n’est pas compatible avec le shell POSIX ou d’autres implémentations basiques mais est vraisemblablement disponible dans les autres implémentations évoluées telles que zsh.

J'ai initialement publié ce document ici. Ce document se veut un peu plus complet et ne contient pas de rappels de commandes élémentaires comme wc. J'ai également écrit un tutoriel sur le shell.

Corriger le caractère de fin de ligne

Pour changer les caractères de fin de ligne d'un fichier de \r\n (windows) à \n (Unix), (l'option -i fait en sorte que la modification se fasse dans le même fichier).

sed -i 's/\r$//' file

Enlever les shebang de plusieurs fichiers

# Remove shebang
for lib in $RPM_BUILD_ROOT/svgwrite/*.py; do
  sed -i '1{\@^#!/usr/bin/env python@d}' $lib
done

Opérations avancées sur les variables

Valeurs par défaut

Il est tout à fait possible de donner une valeur par défaut à une variable en bash. Ainsi, le code suivant : FOO=${FOO:-coucou} signifie que :

  • Si la variable FOO est définie, alors ne rien faire
  • Si FOO n’est pas définie, alors FOO vaut coucou

Cela fonctionne aussi avec une autre variable : FOO=${toto:-coucou}. Cette fois, FOO prendra la valeur coucou si toto n’est pas définie. Cette forme laisse toto inchangée. Pour affecter coucou a foo et toto : FOO=${toto:=coucou}.

Travailler avec les chemins

Le shell dispose de moyen de travailler avec les chemins de façon facile et agréable :

  • Supprimer le dernier / : ${a%/}
  • Supprimer le premier / : ${#*/}
  • Récupérer le dossier courant : ${a##*/} (si a ne se termine pas par /)
  • Récupérer le dossier parent : ${a%/*}

En vérité, vous pouvez remplacer le / par n'importe quel autre caractère (un point par exemple). Voir supprimer une partie de chaîne pour plus de détails.

Manipulation avancée de chaînes de caractères

Extraire une sous-chaîne (bash uniquement)

Simplement avec la syntaxe ${chaine:début:longueur}.

chaine="Trucs et astuces en shell"
echo ${chaine:0:16} # Affiche "Trucs et astuces"

Ou avec cut :

cut -cN-M # N : position du début, M : position de la fin

Remplacer une sous-chaîne par une autre (bash uniquement)

On peut remplace la première occurrence uniquement :

chaine_de_base="J'aime les pommes et les framboises"
remplacer_pomme_par="fraises"
chaine_corrigee=${chaine_de_base/pommes/$remplacer_pomme_par}

On peut aussi remplacer toutes les occurrences avec :

chaine_corrigee=${chaine_de_base//pommes/$remplacer_pomme_par}

On peut aussi remplacer le début ou la fin :

fichier="/root/script.py"

echo ${fichier/#\/root/\/tmp}
echo ${fichier/%.*/.sh}

Ce qui donne :

/tmp/script.py
/root/script.sh

Passer du texte en majuscule/minuscule (bash uniquement)

a=COUCOU
echo ${a,,} # affiche "coucou"
a=coucou
echo ${a^^} # affiche "COUCOU"

On peut aussi passer juste la première lettre en majuscule/minuscule :

a=coucou
echo ${a^} # affiche "Coucou"

a=Coucou
echo ${a,} # affiche "coucou"

Supprimer une partie de chaîne

Pour supprimer la plus courte partie d'une string en partant du début, il suffit d'utiliser la syntaxe ${chaine#sous-chaine}. Pour partir de la fin : ${chaine%sous-chaine}.

fichier="/var/tmp/"

echo ${fichier#*/} # Affiche "var/tmp/"
echo ${fichier%/*} # Affiche "/var/tmp"

Pour supprimer la plus longue partie possible d'une chaîne, il suffit de doubler le caractère :

fichier="/var/tmp/toto"
echo ${fichier##*/} # Affiche toto

fichier=var/tmp/toto
echo  ${fichier%%/*} # Affiche var

Générer des nombres aléatoires

  • Tire un nombre aléatoirement entre 0 - 32767 (bash uniquement) :

    $RANDOM
    
  • Si RANDOM n’est pas disponible, la commande fortune qui tire aléatoirement un proverbe l’est peut être. On en prend la somme pour obtenir un nombre.

    fortune | cksum | cut -f1 -d" "
    
  • Utilise la date où le PID du dernier processus.

    seed=$(( echo $$ ; time ps ; w ; date ) | cksum | cut -f1 -d" ")
    
  • Utilise l’entrée d’entropie du noyau. C’est la meilleur méthode, la plus robuste pour générer de l’aléatoire.

    dd if=/dev/urandom count=1 2> /dev/null | cksum | cut -f1 -d" "
    

Savoir si une chaîne est contenue dans une autre

  • Avec la fonction test (bash uniquement)

    [[ reg_exp =~ contenant ]]
    
  • Avec grep

    grep -q "reg_exp" chaine
    

Renommer les fichiers en fonction d’une expression régulière

for file in `ls` ; do
  new_name=`echo $file | sed 's/\(.*\)\.dump\(.*\)\.sql/\2.\1.sql/'`
  mv $file $new_name
done

On peut également utiliser Emacs.

Savoir si un script est lancé dans un shell interactif

La commande tty -s renvoie 0 si le script est lancé dans un terminal interactif et 1 sinon.

Chmod a+X

chmod a+X va donner les droits d'exécution à un fichier uniquement si les droits d'exécution sont présents pour l'utilisateur, le groupe ou tous. Par exemple :

$ mkdir test
$ mkdir test/folder
$ mkdir -m 600 test/weired_folder
$ touch test/script
$ chmod +x test/script
$ touch test/file

Les droits sont donc :

-rw-r--r--.  1 jenselme jenselme   0 Feb 20 23:02 file
drwxr-xr-x.  2 jenselme jenselme  40 Feb 20 23:01 folder
-rwxr-xr-x.  1 jenselme jenselme   0 Feb 20 23:02 script
drw-------.  2 jenselme jenselme  40 Feb 20 23:02 weired_folder
$ chmod -R a+X

Maintenant les droits sont :

-rw-r--r--.  1 jenselme jenselme   0 Feb 20 23:02 file
drwxr-xr-x.  2 jenselme jenselme  40 Feb 20 23:01 folder
-rwxr-xr-x.  1 jenselme jenselme   0 Feb 20 23:02 script
drwx--x--x.  2 jenselme jenselme  40 Feb 20 23:02 weired_folder

Différences entre les implémentations de certaines commandes entre GNU et BSD

sed

La commande sed de BSD ne connaît pas certains métacaractères tels que \s.

Les opérations dans un fichier se font :

  • sed -i'' 's/toto/tata/g' fichier sous GNU mais
  • sed -i '' 's/toto/tata/g' fichier sous BSD. La commande
  • sed -i -e s/toto/tata/g' fichier est compatible avec les deux versions.