Petit script shell pour créer des liste TODO¶
C'est ma première incursion dans les script shell. Ce code n'est donc surement pas bien optimisé et il peut contenir des erreurs.
Mon but pour ce projet était tout d'abord d'apprendre les bases des script shell. Plus précisément j'ai voulu écrire un petit programme qui permete de créer un mémo.
Voici le code final :
#! /bin/sh
FILE=.todo
touch $FILE
COMMAND=$1 #the first argument (add remove check uncheck renum show or empty)
shift #discard the first argument
case $COMMAND in
add)
for X in "$@" # quotes for adding sentences
do
LASTLINE=$(tail -n 1 $FILE)
NUMBER=$((${LASTLINE%% *} + 1))
echo "$NUMBER [ ] $X" >> $FILE
done
;;
remove)
for X in $@
do
touch tmp_file
#-v to only keep the lines that do not contain the search term
#-w whole word search so that typing 2 does not remove line 12
grep -vw "$X" $FILE > tmp_file
mv tmp_file $FILE
done
;;
check)
for X in $@
do
LINE=$(grep -w "$X" $FILE) #find the entire line
NUMBER=${LINE%% *} #keep only the beginning of the line
ITEM=${LINE##*] } #only the end
sed -i "s/$NUMBER \[ \] $ITEM/$NUMBER \[X\] $ITEM/g" $FILE
done
#echo -e "\e[9m$LINE\e[0m" >> $FILE #other way of checking items
;;
uncheck)
for X in $@
do
LINE=$(grep -w "$X" $FILE)
NUMBER=${LINE%% *}
ITEM=${LINE##*] }
sed -i "s/$NUMBER \[X\] $ITEM/$NUMBER \[ \] $ITEM/g" $FILE
done
;;
renum)
NEW_NUMBER=1
cat $FILE | while read LINE
do
NUMBER=${LINE%% *}
sed -i "s/\<$NUMBER\>/$NEW_NUMBER/" $FILE #\< \> to match whole word
((NEW_NUMBER+=1))
done
;;
show)
cat $FILE
;;
empty)
rm $FILE
touch $FILE
;;
help)
echo "todo add thing1 thing2 #to add items to the list"
echo "todo remove 1 thing2 #to remove item 1 and item containing thing2"
echo "todo check 1 thing2 #to mark item as done"
echo "todo uncheck 1 thing2 #to remove marks"
echo "todo show"
echo "todo empty"
echo "todo renum"
;;
*)
echo "Invalid option: try todo help"
;;
esac
Et voici un exemple d'utilisation (le lignes commençant par $ représentent le prompt) :
$ todo add banane
$ todo add pomme orange kiwi
$ todo add "faire exo 3 page 193"
$ todo show
1 [ ] banane
2 [ ] pomme
3 [ ] orange
4 [ ] kiwi
5 [ ] faire exo 3 page 193
$ todo check 1 pomme 4
$ todo show
1 [X] banane
2 [X] pomme
3 [ ] orange
4 [X] kiwi
5 [ ] faire exo 3 page 193
$ todo uncheck 2
$ todo show
1 [X] banane
2 [ ] pomme
3 [ ] orange
4 [X] kiwi
5 [ ] faire exo 3 page 193
$ todo remove X
$ todo show
2 [ ] pomme
3 [ ] orange
5 [ ] faire exo 3 page 193
$ todo remove pomme
$ todo show
3 [ ] orange
5 [ ] faire exo 3 page 193
$ todo add stylo cahier gomme
$ todo show
3 [ ] orange
5 [ ] faire exo 3 page 193
6 [ ] stylo
7 [ ] cahier
8 [ ] gomme
$ todo renum
$ todo show
1 [ ] orange
2 [ ] faire exo 1 page 193
3 [ ] stylo
4 [ ] cahier
5 [ ] gomme
Explication¶
La première ligne est un "shebang". Il permet de préciser quel interpréteur (sh, bash, zsh, python...) utiliser pour exécuter le scrip.
On affecte alors à la variable FILE le nom du fichier dans lequel on veut que la liste soit écrite. Puis on s'assure que le fichier existe avec la commande :
touch $FILE
En shell on utilise "$" pour indiquer qu'on veut utiliser la valeur de la variable et non pas la chaîne de caractères.
Donc FILE correspont à la chaîne de caractères "FILE" alors que $FILE est la valeur de la variable FILE qui vaut ici .todo .
Pour lire les arguments donnés au script on utilise $1, $2, $3 ... pour les argumets 1, 2, et 3, respectivement. Attention on commence à compter à 1 et non pas à 0 (techniquement $0 est le nom du programme).
COMMANT=$1
Sert donc à savoir quelle est la commande qui doit être executée (add, remove, check, show...).
(On fait aussi un "shift" dont l'utilité sera expliqué plus tard)
Un case switch sert alors à exécuter des commandes différentes en fonction de la commande spécifié par l'utilisateur.
La syntaxe d'un case en shell est :
case variable in
pattern1)
do
...
;;
pattern2)
do
...
;;
esac
add¶
Je voulais que l'on puisse ajouter plusieurs items à la liste d'un coup, mais aussi que l'on puisse ajouter des phrases :
$ todo add tomate banane salade
Donne :
1 [ ] tomate
2 [ ] banane
3 [ ] salade
Mais
$ todo add "tomate banane salade"
Donne :
1 [ ] tomate banane salade
Cela est accomplit en itérant sur les arguments avec "$@" :
for X in "$@"
do
...
done
X va prendre les valeurs des arguments, plusieurs mots séparés par des espaces comptent comme différents arguements, sauf s'il sont entre " ".
Comme un shift est réalisé dans la première partie du code, le premiers argument (add, remove, check...) à été suprimé et les indices des arguments suivants ont été décalés.
Je voulait aussi que la numérotation des items soit toujours croissante, même si des éléments ont étés supprimés. Pour cela il faut connaitre le numéro du dernier itém, mais simplement compter le nombre de lignes ne suffit pas.
Il faut donc extraire la dernière ligne du fichier :
LASTLINE=$(tail -n 1 $FILE)
Puis extraire le numéro :
NUMBER=$((${LASTLINE%% *} + 1))
Les syntaxes${VAR%%séparateur}
${VAR##séparateur}
sont très pratiques et sont utilisés plusieurs fois dans ce script. La première signifie garder tout jusqu'au séparateurs et la deuxière garder tout après le séparateur. EX :$ VAR="apple orange"
$ echo "${VAR%% }"
apple
$ echo ${VAR## }
orange
Il ne reste plus qu'a ajouter a nouvelle ligne à la fin du fichier :
echo "$NUMBER [ ] $X" >> $FILE
ajoute la ligne à la fin du fichier alors que > écrase le contenu du fichier.
remove¶
Ceci est assez simple à implémenter grâce à grep. Notament avec le flag -v, qui renvoie uniquement les lignes qui n'ont pas de coincidence.
Avec cette simple implémentation on peut effacer les lignes en donnant leur numéro, une partie de l'item ou même effacer tous les éléments qui ont étés marqués par un X avec la commande check.
Le flag -w permet d'effacer la ligne qui si le mot entier est présent.
check et uncheck¶
Ces deux commandes utilisent l'option "substitute" de sed pour remplacer [ ] par [X] et vice versa.
renum¶