====== Memo BASH ======
* [[http://abs.traduc.org/abs-fr/index.html|Guide avancé d'écriture des scripts Bash]]
* http://www.davidpashley.com/articles/writing-robust-shell-scripts/
* https://www.shellcheck.net/
* https://bash3boilerplate.sh/
* https://devhints.io/bash
* https://devdocs.io/bash/
===== vrac =====
==== Redirection ====
Pour rediriger les erreurs dans le meme log : commande >log 2>&1
* http://www.tldp.org/LDP/abs/html/ioredirintro.html et http://www.tldp.org/LDP/abs/html/ioredirintro.html
* http://wiki.bash-hackers.org/howto/redirection_tutorial#an_example pour un exemple instructif de file descriptors
* http://xensoft.com/content/use-exec-direct-all-bash-script-output-file-syslog-or-other-command pour capture de stdout|stderr dans un script
Cf aussi
trap commande signal
pour exécuter une commande en cas de signal (le try/catch du bash)
Attention, si on capture stdout avec un truc comme
exec > >(while read line; do echo "[$(date '+%F %T')] $line" >> $LOGRAP; done)
le contenu du fichier $LOGRAP est pas immédiatement disponible dans le script principal (quand un sous-shell écrit dans un fichier, faut visiblement qu'il ait terminé pour que le parent soit sûr de récupérer le contenu.
==== gestion des processus liés au terminal (avant/arrière plan, attachés/détachés) ====
Dans un terminal bash (probablement pareil avec d'autre shells), pour
* lancer une commande en arrière plan, ajouter "&" à la fin,
* basculer avant-plan ariière-plan, c'est fg/bg (foreground/background)
* lancer une commande détachée du terminal (la tâche ne sera pas tuée à la fermeture du terminal), c'est ''nohup''
* pour la détacher alors qu'elle est déjà lancée, "ctrl+Z" pour la stopper (sans l'arrêter) pour reprendre la main, puis ''bg'' pour la passer en arrière plan (on peut vérifier avec ''jobs'') et ''disown'' (ça va détacher tous les processus en arrière plan du terminal courant)
(cf man bash et http://www.siteduzero.com/tutoriel-3-67789-executer-des-programmes-en-arriere-plan.html)
==== jobs et at ====
On peut lister les jobs en cours avec ''jobs -p'', mais ça ne concernent pas les jobs en attente de at (du genre ''echo 'faire un truc' | at 14:42''), qui sont dans /var/spool/cron/atjobs/, mais que l'on peut lister avec ''atq'' ou virer avec ''atrm $numero_du_job''.
==== variables ====
pour ajouter un rep au path : export PATH=$PATH:/le/rep/a/ajouter
ajouter des nb
let "nb += 1" incremente de 1
%%echo $((35+1*5)) ou nb=$(($nb*3+1)) pour des operations%%
Modulo
nb=$RANDOM
let "nb %= 1000" #un modulo pour ramener nb entre 0 et 1000
Variables accessibles dans un script
$? C'est la valeur de sortie de la dernière commande. Elle vaut 0 si la commande s'est déroulée sans pb.
$0 Cette variable contient le nom du script
$1 à $9 Les (éventuels) premiers arguments passés à l'appel du script
$# Le nombre d'arguments passés au script
$* La liste des arguments à partir de $1
$@ La liste des arguments à partir de $1, chacun étant entre " (le nb et les contenu sont donc préservé si l'un deux comporte une espace, cf http://stefaanlippens.net/node/85, donc en général ce sera du ''for arg in "$@"...'')
''shift'' enlève $1 de la liste (ne le retourne pas, faut le mémoriser dans une variable avant si on en a besoin).
$$ le n° PID du processus courant
$! le n° PID du processus fils
==== Manipulation de chaînes ====
Regarder la manipulation de chaines sur http://abs.traduc.org/abs-fr/ch09s02.html et celle de nombres sur http://abs.traduc.org/abs-fr/ch09s07.html
Attention, globing correspond à une expression de "globing shell" (to*tu => commence par 'to' et fini par 'tu'), pas à une regex sed egrep ou autre.
${#chaine} # nb de car de $chaine
expr match "$chaine" 'globing' # longueur max de globing à partir du début de $chaine
expr match "tatotu" 'to' # => 0
expr match "tatotu" '[at]*o' # => 4
expr index $chaine $souschaine # Position numérique dans $chaine du premier caractère dans $souschaine qui correspond.
expr index "tatotu" 'ua' # => 2
# Extraction d’une sous-chaîne
${chaine:pos} # Extrait une sous-chaîne de $chaine à partir de la position pos.
${chaine:pos:longueur} # Extrait long caractères de $chaine en démarrant à la position pos.
# Suppression de sous-chaînes
${chaine#globing} # Supprime la correspondance la plus petite de globing à partir du début de $chaine.
${chaine##globing} # Supprime la correspondance la plus grande de globing à partir du début de $chaine.
${chaine%globing} # Supprime la plus petite correspondance de globing à partir de la fin de $chaine.
${chaine%%globing} # Supprime la plus grande correspondance de globing à partir de la fin de $chaine.
# Remplacement de sous-chaîne
${chaine/globing/remplacement} # Remplace dans $chaine la première correspondance de globing par "remplacement".
${chaine//globing/remplacement} # Remplace dans $chaine toutes les correspondances de globing avec "remplacement".
# ex pour échapper les slashes
chaine=/path/2/fichier; echo ${chaine//\//\\/} # => \/path\/2\/fichier
${chaine/#globing/remplacement} # Si globing correspond au début de $chaine, remplace globing (gourmand) par "remplacement".
# ex pour faire un basename
chaine=/path/2/fichier.ext; echo ${chaine/#*\//} # => fichier
${chaine/%globing/remplacement} # Si globing correspond à la fin de $chaine, remplace globing (gourmand) par "remplacement".
chaine=/path/2/fichier.ext.ext2; echo ${chaine/%.*/} # => /path/2/fichier
==== substitution de variable ====
bash ne permet pas l'écriture de $$var ou ${$var}. Pour contourner ce pb, utiliser les tableaux ${var[$indice]} (si $indice n'est pas numérique, il est converti en 0, on ne peut donc pas avoir de clés non numériques)
Mais pour des booléens on peut utiliser le test -v "$nomVar" pour savoir si elle a été définie ou pas (mais on peut pas récupérer sa valeur).
==== tableaux ====
Cf http://aral.iut-rodez.fr/fr/sanchis/enseignement/bash/ar01s12.html
Pour avoir les index d'un tableau :
$ arr=([1]=coucou bonjour [5]=hello)
$ echo ${!arr[*]}
1 2 5
Ce qui permet de parser un tableau avec
$ arr=([1]=coucou bonjour [5]=hello)
$ for i in ${!arr[*]}; do echo "$i => ${arr[i]}"; done; # noter le ${arr[i]}, ${arr[$i]} marche aussi
1 => coucou
2 => bonjour
5 => hello
==== tests ====
Source: http://www.ac-creteil.fr/reseaux/systemes/linux/shell-scripts/shell-programmation.html et http://abs.traduc.org/abs-fr/ch07s02.html
* Tester un fichier [ option fichier ]
* -e : il existe
* -f : c'est un fichier normal
* -d : c'est un répertoire
* -L : c'est un symlink
* -h : c'est un symlink valide (marche pour les dossiers)
* -r | -w | -x | -k | -p | -u : lisible | modifiable | exécutable | sticky | suid
* -p | -t | -S : tube nommé | file descriptor | socket
* -O | -G : il m' appartient | appartient à mon groupe
* -s : il n'est pas vide
* fichier_1 -ef fichier_2 : vrai si le fichier_1 et fichier_2 sont hard linkés (equal files)
* fichier_1 -nt fichier_2 : vrai si le fichier_1 est plus récent (newer than) que le fichier_2 ou si fichier_1 existe et non fichier_2
* Tester une chaine [ option chaine ]
* -z | -n la chaine est vide / n'est pas vide
* = | != les chaines comparées sont identiques | différentes
* -v variable : $variable existe et a été assignée
ATTENTION à quoter la variable à tester :
t=''; [ -n $t ]; echo "-n : $?"; [ -z $t ]; echo "-z : $?";
# renvoie
# -n : 0
# -z : 0
t=''; [ -n "$t" ]; echo "-n : $?"; [ -z "$t" ]; echo "-z : $?";
# renvoie
# -n : 1
# -z : 0
* Tester un nombre
[ nb1 option nb2 ]
-eq | -ne égal | différent
-lt | -gt strict. inf | strict. sup
-le | -ge inf ou égal | sup ou égal
et on peut combiner ça avec du -a (and) -o (or) et ! (not). Par exemple\\
[ ! -f $1 -a $2 -lt 10 ] && echo "OK, le 1er param n'est pas un fichier et le 2e est inférieur à 10"
==== ulimit ====
* -S :soft limit
* -H : hard limit
* -a : tout afficher
* -n : nb max de fichiers ouverts (socket & connexions comprises)
* -u : nb max de process lancés
* -T : nb max de threads
Cf man bash /ulimit pour le reste... (ou man dash, sous debian les process sont souvent lancés par dash, alias de sh, cf /etc/passwd)
Ces valeurs sont fixées dans /etc/security/limits.conf (par user/group), où dans certains fichiers de paramètres des script de démarrage (/etc/default/nginx par ex).
Attention, ces valeurs s'héritent (par ex un "sudo -i -u toto ulimit -a" n'affiche pas forcément la même chose qu'un passthru("ulimit -a") exécuté par un pool php qui tourne sous le user toto, qui lui a hérité des valeurs du process php-fpm parent, cf /etc/default/php5-fpm)
===== Commandes perso =====
* Pour lister le nb de fichiers/dossiers par dossier du rep courant (cf http://serverfault.com/a/111860)
ionice -c3 find | cut -d/ -f2 > /tmp/files.list
uniq -c < /tmp/files.list | sort -n
==== Scripts ====
=== afflign.sh - afficher une (ou des) lignes d'un fichier ===
usage /root/scripts/afflign.sh N fichier\\
(N est la ligne de fichier à afficher ou un intervalle sous la forme N,M)
===== Commandes =====
Cf http://ss64.com/bash/ pour une liste de commandes bash et programmes gnu
==== vrac ====
netstat -tanpu | grep "0.0.0.0" pour voir les ports ouverts
netstat -tap |grep -e "EST.*httpd" donne les connexions http actives (ESTABLISHED)
netstat -tap |grep httpd donne les connexions http
voir le debut d'un fichier : head
la fin : tail (tail -f affiche la fin au fur et a mesure que le fichier grossit)
page man dans un fichier
man commande|col -b>fichier
un exemple de for, avec 2 instruction dans le do, sur une ligne:
for log in /www/logs/*.log; do echo $log; cat $log | grep "194.254.29.133" | wc -l; done
pour recupere un bout de variable texte (cf Login n°115 p76)
for fich in tmp_*; do echo ${fich#tmp_*}; done;
n'affiche que ce qui est apres le tmp_
for fich in *.log; do echo ${fich%*.log}; done;
n'affiche que le nom du fichier sans le .log de la fin
for fich in ./*; do echo ${fich%%.*}; done; # jusqu'au 1er point
for fich in ./*; do echo ${fich%.*}; done; # jusqu'au dernier point
for fich in ./*; do echo ${fich##*.}; done; # ce qui reste apres le dernier point
for fich in ./*; do echo ${fich#*.}; done; # ce qui reste apres le premier point
==== find ====
pour afficher tous les fichiers de + de 20Ko (FreeBSD, notation 20k autorisée sous linux)
find /path \( -size +20000c \) -exec ls -l {} \;
lister les fichier de /tmp de 0 octets de + de 60mn
find /tmp -size 0 -mmin +60 -exec ls -alh {} \;
pour renommer les mp3 en swf
for i in *.mp3; do mv $i ${i%mp3}swf ;done
==== sed ====
fait du rechercher/remplacer sur des streams (avec regexp) : sed 'action/rech/rempl/opt
cf [[sed]]
exemples sur http://iml.univ-mrs.fr/~bac/DESS/sed.html
ex: cette commande prend le fichier didiersvt.com.hosts et substitue (le s/ du début) didiersvt.com par didierxl.com dans tout le fichier (option /g à la fin).
cat didiersvt.com.hosts | sed 's/didiersvt.com/didierxl.com/g' > didierxl.com.hosts
autre exemple :
for fich in *.hosts; do cat $fich | sed 's/ns339/ns2230/g' > $fich; done
remplace ns339 par ns2230 dans tous les fichiers .hosts qu'il trouve dans le rep courant
for fich in *.hosts; do fichrac=`echo $fich | sed 's/.old//g'`; echo $fichrac; done
met dans fichrac le contenu de fich sans le .old à la fin
en combinant ça avec des regexp ça donne
for fich in *.old; do fichrac=`echo $fich | sed 's/.old//g'`;cat $fich | sed 's#^\([^ ]*\)\([[:blank:]]*\)IN\([[:blank:]]*\)NS\([[:blank:]]*\)ns6.gandi.net.$#\1\2IN\3NS\4ns6.gandi.net.\
\1\2IN\3NS\4ns.ovh.net.#g' > $fichrac; done
pour ajouter le ns.ovh.net. en plus de gandi dans tous les fichiers hosts à partir des old
Noter le retour chariot dans le remplacement avec \ et la suite sur 2e ligne.
Début de ligne ^
Fin de ligne $
N'importe quel caractère .
Zéro ou plusieurs *
Un ou plusieurs \+
a ou b ou c a\|b\|c
'(' (
')' )
'[' \[
']' \]
Caractère de l'ensemble ... [...]
Caractère n'appartenant à l'ensemble ... [^...]
Capturer des caractères \(...\)
Première référence \1
autre ex for & sed
for fich in *.hosts; do digovh.sh `echo $fich | sed 's/.hosts//g'` | grep ko; done
Voir aussi tr qui remplace des caractères (sans regexp, lire le man pour les pbs avec l'utf8).
sed est aussi efficace pour afficher une ligne particulière (ici la ligne 288)
sed -e '288 !d' /fichier.sql ; done
pour récupérer les GUIDuser du log nl3i
grep kne nl3i_access.log | grep 'GET /kne/index.php?GUIDUser' | cut -d' ' -f7 | cut -d= -f2 | cut -d'&' -f1
==== mysql ====
mysql en console:
mysql -uuser -ppass : se connecte sur mysql avec user/pass
mysql -uuser -ppass -e"commande a passer" : se connecte sur mysql avec user/pass, execute la commande et sort
Quand on a perdu le mot de pase root:
/etc/rc.d/init.d/mysql stop
# Restart it with the following:
/usr/bin/safe_mysqld --skip-grant-tables --skip-networking &
# Connect to the server with:
mysql mysql
# Enter the following:
UPDATE user SET password=PASSWORD('the_new_password') WHERE User="root" AND Host="localhost";
# Exit the client
# Shut down the server with:
mysqladmin shutdown
# Start the server back up properly with:
/etc/rc.d/init.d/mysql start
trouvé sur http://cobalt-knowledge.sun.com/cgi-bin/kbase.cfg/php/enduser/std_adp.php?p_sid=YjolZNgg&p_lva=&p_refno=011220-000002&p_created=1008866547&p_sp=cF9ncmlkc29ydD0mcF9yb3dfY250PTk5OSZwX3BhZ2U9MQ**&p_li=
ex de backup & mail
#!/bin/sh
nom_fichier=sql_backup-$(date -I).tar.gz
fichier_attache=/home/log/$nom_fichier
sujet="sauvegar sql: $nom_fichier"
envoyer_a=mail@domain.tld
passmysql=XXXXXX
/usr/bin/mysqldump -A -u root -p$passmysql |gzip > $fichier_attache ;
/usr/bin/uuencode $fichier_attache $nom_fichier| mail -s "$sujet" $envoyer_a;
==== mail ====
un simple
cat fichier_a_joindre | uuencode nom_de_la_piece_jointe | mail -s "sujet" dest@domaine.tld
marche aussi, équivalent à
uuencode fichier_a_joindre nom_de_la_piece_jointe | mail -s "sujet" dest@domaine.tld
===== NFS =====
Les répertoires à exporter et les ip vers lesquelles exporter sont dans /etc/exports.
Suite à une modif de ce fichier, il faut réexporter tout pour prendre les changements en compte :
exportfs -vr
Pour modifier uid et gid à la volée, voici des options à mettre dans /etc/exports
squash_uids=1000 : l'uid 1000 d'un client qcq sera modifiée vers l'uid anonyme du serveur
squash_gids=1000 : idem gid 1000
anonuid=150 : pour ce montage, l'uid anonyme à prendre est 150
anongid=100 : pour ce montage, le gid anonyme à prendre est 100
Donc, par exemple, si daniel ayant l'ip 192.168.92.181, veut monter /home/www sous 500:500 (coté serveur) alors qu'il est 1000:1000 (côté client) il faut mettre :
/home/www 192.168.92.181(rw,sync,root_squash,squash_uids=1000,squash_gids=1000,anonuid=500,anongid=500)