En tant qu'administrateur système, les shells font partie des opérations quotidiennes. Les shells offrent souvent plus d'options et de flexibilité qu'une interface utilisateur graphique (GUI). Les tâches répétitives quotidiennes peuvent facilement être automatisées par des scripts, ou les tâches peuvent être programmées pour s'exécuter à certains moments de la journée. Un shell offre un moyen pratique d'interagir avec le système et vous permet d'en faire plus en moins de temps. Il existe de nombreux shells différents, notamment Bash, zsh, tcsh et PowerShell.
Dans cet article de blog en deux parties, je partage certains des one-liners Bash que j'utilise pour accélérer mon travail et laisser plus de temps pour boire du café. Dans ce premier article, je couvrirai l'historique, les derniers arguments, l'utilisation des fichiers et des répertoires, la lecture du contenu des fichiers et les fonctions Bash. Dans la deuxième partie, j'examinerai les variables shell, la commande find, les descripteurs de fichiers et l'exécution d'opérations à distance.
Utilisez la commande d'historique
L'history
la commande est pratique. History
me permet de voir quelles commandes j'ai exécutées sur un système particulier ou quels arguments ont été passés à cette commande. J'utilise history
pour relancer des commandes sans avoir à se souvenir de quoi que ce soit.
L'enregistrement des commandes récentes est stocké par défaut dans ~/.bash_history.
Cet emplacement peut être modifié en modifiant la variable shell HISTFILE. Il existe d'autres variables, telles que HISTSIZE (lignes à stocker en mémoire pour la session en cours) et HISTFILESIZE (combien de lignes à conserver dans le fichier historique). Si vous voulez en savoir plus sur l'history
, voir man bash
.
Disons que j'exécute la commande suivante :
$> sudo systemctl status sshd
Bash me dit que le service sshd ne fonctionne pas, donc la prochaine chose que je veux faire est de démarrer le service. J'avais vérifié son statut avec ma commande précédente. Cette commande a été enregistrée dans history
, afin que je puisse le référencer. Je lance simplement :
$> !!:s/status/start/
sudo systemctl start sshd
L'expression ci-dessus a le contenu suivant :
- !! - répéter la dernière commande de l'historique
- :s/status/start/ - remplace status avec start
Le résultat est que le service sshd est démarré.
Ensuite, j'augmente la valeur par défaut de HISTSIZE de 500 à 5000 en utilisant la commande suivante :
$> echo “HISTSIZE=5000” >> ~/.bashrc && source ~/.bashrc
Et si je veux afficher les trois dernières commandes de mon historique ? Je saisis :
$> history 3
1002 ls
1003 tail audit.log
1004 history 3
Je lance tail
sur audit.log
en se référant au numéro de la ligne d'historique. Dans ce cas, j'utilise la ligne 1003 :
$> !1003
tail audit.log
..
..
Imaginez que vous avez copié quelque chose depuis un autre terminal ou votre navigateur et que vous collez accidentellement la copie (que vous avez dans le tampon de copie) dans le terminal. Ces lignes seront stockées dans l'historique, ce que vous ne voulez pas. C'est donc là que unset HISTFILE &&exit est pratique
$> unset HISTFILE && exit
ou
$> kill -9 $$
Référence au dernier argument de la commande précédente
Lorsque je souhaite répertorier le contenu des répertoires de différents répertoires, je peux changer de répertoire assez souvent. Il existe une astuce intéressante que vous pouvez utiliser pour faire référence au dernier argument de la commande précédente. Par exemple :
$> pwd
/home/username/
$> ls some/very/long/path/to/some/directory
foo-file bar-file baz-file
Dans l'exemple ci-dessus, /some/very/long/path/to/some/directory
est le dernier argument de la commande précédente.
Si je veux cd
(changer de répertoire) à cet emplacement, je saisis quelque chose comme ceci :
$> cd $_
$> pwd
/home/username/some/very/long/path/to/some/directory
Maintenant, utilisez simplement un tiret pour revenir là où j'étais :
$> cd -
$> pwd
/home/username/
Travailler sur des fichiers et des répertoires
Imaginez que je veuille créer une structure de répertoires et déplacer un tas de fichiers ayant différentes extensions vers ces répertoires.
Tout d'abord, je crée les répertoires en une seule fois :
$> mkdir -v dir_{rpm,txt,zip,pdf}
mkdir: created directory 'dir_rpm'
mkdir: created directory 'dir_txt'
mkdir: created directory 'dir_zip'
mkdir: created directory 'dir_pdf'
Ensuite, je déplace les fichiers en fonction de l'extension de fichier vers chaque répertoire :
$> mv -- *.rpm dir_rpm/
$> mv -- *.pdf dir_pdf/
$> mv -- *.txt dir_txt/
$> mv -- *.zip dir_txt/
Les caractères à double tiret --
signifie Fin des options. Cet indicateur empêche les fichiers commençant par un tiret d'être traités comme des arguments.
Ensuite, je veux remplacer/déplacer tous les fichiers *.txt vers des fichiers *.log, donc j'entre :
$> for f in ./*.txt; do mv -v ”$file” ”${file%.*}.log”; done
renamed './file10.txt' -> './file10.log'
renamed './file1.txt' -> './file1.log'
renamed './file2.txt' -> './file2.log'
renamed './file3.txt' -> './file3.log'
renamed './file4.txt' -> './file4.log'
Au lieu d'utiliser le for
boucle ci-dessus, je peux installer le prename
commandez et accomplissez l'objectif ci-dessus comme ceci :
$> prename -v 's/.txt/.log/' *.txt
file10.txt -> file10.log
file1.txt -> file1.log
file2.txt -> file2.log
file3.txt -> file3.log
file4.txt -> file4.log
Souvent, lors de la modification d'un fichier de configuration, je fais une copie de sauvegarde de l'original en utilisant une commande de copie de base. Par exemple :
$> cp /etc/sysconfig/network-scripts/ifcfg-eth0 /etc/sysconfig/network-scripts/ifcfg-eth0.back
Comme vous pouvez le voir, répéter tout le chemin et ajouter .back au fichier n'est pas si efficace et probablement sujet aux erreurs. Il existe un moyen plus court et plus propre de le faire. Le voici :
$> cp /etc/sysconfig/network-scripts/ifcfg-eth0{,.back}
Vous pouvez effectuer différentes vérifications sur des fichiers ou des variables. Exécutez le help test
pour plus d'informations.
Utilisez la commande suivante pour découvrir si un fichier est un lien symbolique :
$> [[ -L /path/to/file ]] && echo “File is a symlink”
Voici un problème que j'ai rencontré récemment. Je voulais gunzip/untar un tas de fichiers en une seule fois. Sans réfléchir, j'ai tapé :
$> tar zxvf *.gz
Le résultat était :
tar: openvpn.tar.gz: Not found in archive
tar: Exiting with failure status due to previous errors
Les fichiers tar étaient :
iptables.tar.gz
openvpn.tar.gz
…..
Pourquoi cela n'a-t-il pas fonctionné, et pourquoi ls -l *.gz
travailler à la place ? Sous le capot, ça ressemble à ça :
$> tar zxvf *.gz
Se transforme comme suit :
$> tar zxvf iptables.tar.gz openvpn.tar.gz
tar: openvpn.tar.gz: Not found in archive
tar: Exiting with failure status due to previous errors
Le tar
La commande devrait trouver openvpn.tar.gz dans iptables.tar.gz. J'ai résolu ce problème avec un simple for
boucle :
$> for f in ./*.gz; do tar zxvf "$f"; done
iptables.log
openvpn.log
Je peux même générer des mots de passe aléatoires en utilisant Bash ! Voici un exemple :
$> alphanum=( {a..z} {A..Z} {0..9} ); for((i=0;i<=${#alphanum[@]};i++)); do printf '%s' "${alphanum[@]:$((RANDOM%255)):1}"; done; echo
Voici un exemple qui utilise OpenSSL :
$> openssl rand -base64 12
JdDcLJEAkbcZfDYQ
Lire un fichier ligne par ligne
Supposons que j'ai un fichier avec beaucoup d'adresses IP et que je souhaite opérer sur ces adresses IP. Par exemple, je veux exécuter dig
pour récupérer les informations DNS inversées pour les adresses IP répertoriées dans le fichier. Je souhaite également ignorer les adresses IP qui commencent par un commentaire (# ou hashtag).
Je vais utiliser fileA comme exemple. Son contenu est :
10.10.12.13 some ip in dc1
10.10.12.14 another ip in dc2
#10.10.12.15 not used IP
10.10.12.16 another IP
Je pourrais copier et coller chaque adresse IP, puis exécuter dig
manuellement :
$> dig +short -x 10.10.12.13
Ou je pourrais faire ceci :
$> while read -r ip _; do [[ $ip == \#* ]] && continue; dig +short -x "$ip"; done < ipfile
Et si je veux échanger les colonnes dans le fichier A ? Par exemple, je veux mettre les adresses IP dans la colonne la plus à droite pour que le fichierA ressemble à ceci :
some ip in dc1 10.10.12.13
another ip in dc2 10.10.12.14
not used IP #10.10.12.15
another IP 10.10.12.16
Je cours :
$> while read -r ip rest; do printf '%s %s\n' "$rest" "$ip"; done < fileA
Utiliser les fonctions Bash
Les fonctions dans Bash sont différentes de celles écrites en Python, C, awk ou d'autres langages. Dans Bash, une fonction simple qui accepte un argument et affiche "Hello world" ressemblerait à ceci :
func() { local arg=”$1”; echo “$arg” ; }
Je peux appeler la fonction comme ceci :
$> func foo
Parfois, une fonction s'invoque de manière récursive pour effectuer une certaine tâche. Par exemple :
func() { local arg="$@"; echo "$arg"; f "$arg"; }; f foo bar
Cette récursivité fonctionnera indéfiniment et utilisera beaucoup de ressources. Dans Bash, vous pouvez utiliser FUNCNEST pour limiter la récursivité. Dans l'exemple suivant, j'ai défini FUNCNEST=5 pour limiter la récursivité à cinq.
func() { local arg="$@"; echo "$arg"; FUNCNEST=5; f "$arg"; }; f foo bar
foo bar
foo bar
foo bar
foo bar
foo bar
bash: f: maximum function nesting level exceeded (5)
Utiliser une fonction pour récupérer le fichier le plus récent ou le plus ancien
Voici un exemple de fonction pour afficher le fichier le plus récent dans un certain répertoire :
latest_file()
{
local f latest
for f in "${1:-.}"/*
do
[[ $f -nt $latest ]] && latest="$f"
done
printf '%s\n' "$latest"
}
Cette fonction affiche le fichier le plus ancien d'un certain répertoire :
oldest_file()
{
local f oldest
for file in "${1:-.}"/*
do
[[ -z $oldest || $f -ot $oldest ]] && oldest="$f"
done
printf '%s\n' "$oldest"
}
Ce ne sont là que quelques exemples de la façon d'utiliser les fonctions de Bash sans appeler d'autres commandes externes.
Je me retrouve parfois à taper une commande encore et encore avec beaucoup de paramètres. Une commande que j'utilise souvent est kubectl
(CLI Kubernetes). J'en ai marre de lancer cette longue commande ! Voici la commande d'origine :
$> kubectl -n my_namespace get pods
ou
$> kubectl -n my_namespace get rc,services
Cette syntaxe m'oblige à inclure manuellement -n my_namespace
chaque fois que j'exécute la commande. Il existe un moyen plus simple de le faire en utilisant une fonction :
$> kubectl () { command kubectl -n my_namespace ”$@” ; }
Maintenant, je peux exécuter kubectl
sans avoir à taper -n namespace
à chaque fois :
$> kubectl get pods
Je peux appliquer la même technique à d'autres commandes.
Récapitulez
Ce ne sont là que quelques excellentes astuces qui existent pour Bash. Dans la deuxième partie, je montrerai quelques exemples supplémentaires, y compris l'utilisation de la recherche et de l'exécution à distance. Je vous encourage à pratiquer ces astuces pour rendre vos tâches d'administration en ligne de commande plus faciles et plus précises.
[ Cours en ligne gratuit :Présentation technique de Red Hat Enterprise Linux. ]