Comment puis-je faire en sorte qu'un script se connecte dans un fichier distinct le nombre de fois qu'il a été exécuté?


11

J'ai besoin d'écrire un script qui écrit dans un fichier combien de fois ce script a été exécuté.

Comment puis je faire ça?

Réponses:


15

Je suppose que vous voulez avoir un seul fichier countfilequi ne contient qu'un seul numéro représentant le compteur d'exécution.

Vous pouvez lire ce compteur dans une variable shell, $counterpar exemple en utilisant l'une de ces lignes:

  • read counter < countfile
  • counter=$(cat countfile)

De simples ajouts d'entiers peuvent être effectués dans Bash lui-même en utilisant la $(( EXPRESSION ))syntaxe. Ensuite, écrivez simplement le résultat dans notre countfile:

echo "$(( counter + 1 ))" > countfile

Vous devriez probablement aussi sauvegarder votre script pour le cas qui countfilen'existe pas encore et en créer un initialisé avec la valeur 1.

Le tout pourrait ressembler à ceci:

#!/bin/bash
if [[ -f countfile ]] ; then
    read counter < countfile
else
    counter=0
fi
echo "$(( counter + 1 ))" > countfile

2
… Ou comme ça:echo $(( $(cat countfile 2>/dev/null || echo 0) + 1 )) > countfile
PerlDuck

1
Bien, cela semble fonctionner aussi bien @PerlDuck. J'avais peur qu'il puisse ouvrir le fichier pour l'écraser avant qu'il ne soit lu, mais apparemment la syntaxe de substitution de processus semble empêcher cela.
Byte Commander

Bon point. Je ne suis pas tout à fait sûr de savoir s'il est garanti qu'il $(…)soit exécuté avant toute autre chose (en particulier avant le >). Mais j'utilise souvent la $(cat countfile 2>/dev/null || echo 0)pièce pour obtenir un défaut raisonnable au cas où le fichier n'existe pas. Nous pourrions ajouter un sponge:-) pour être sûr.
PerlDuck

2
Cette réponse serait améliorée en encapsulant ce code de comptage dans une flockcommande pour éviter les conditions de concurrence. Voir unix.stackexchange.com/a/409276
rrauenza

5

Laissez simplement le script créer un fichier journal, ajoutez par exemple une ligne dans votre script à la fin:

echo "Script has been executed at $(date +\%Y-\%m-\%d) $(date +\%H-\%M-\%S)" >> ~/script.log

De cette façon, vous pouvez formater la façon dont vous présentez la date et l'heure vous-même, mais si vous voulez simplement utiliser une date et une heure complètes (et HH:MM:SSc'est un format acceptable pour vous), vous pouvez également utiliser simplement:

echo "Script has been executed at $(date +\%F-\%T)" >> ~/script.log

Ensuite, vous pourriez faire:

wc -l ~/script.log

Qui compte les caractères de nouvelle ligne et vous donne une estimation du nombre de lignes à l'intérieur du fichier journal. Jusqu'à cela, vous pouvez voir dans le fichier journal même lorsqu'il a été exécuté. Pour l'adapter à vos besoins, vous pouvez modifier les chemins et les noms utilisés pour la journalisation. Je viens de faire un exemple ici qui enregistre le fichier journal ~.

Ainsi, par exemple, vous voulez que le script ajoute ce nombre à la ligne que vous avez ajoutée à la fin de votre script, vous pouvez faire quelque chose comme ceci au début de votre script:

count=$(( $(wc -l ~/script.log | awk '{print $1}') + 1 ))
# the next line can be simply skipped if you not want an output to std_out
echo "Script execution number: $count"

Et changez votre ligne à la fin du script en quelque chose incluant même ces informations:

echo "Script has been executed $count times at $(date +\%F-\%T)" >> ~/script.log

5

Cette solution utilise la même approche que la réponse de Byte Commander, mais elle ne repose pas sur l'arithmétique du shell ou d'autres bashismes.

exec 2>&3 2>/dev/null
read counter < counter.txt || counter=0
exec 3>&2 3>&-
expr "$counter" + 1 > counter.txt

Les redirections de flux

  1. dupliquer le flux d'erreur standard (2) vers un descripteur de fichier différent (3),
  2. le remplacer (2) par une redirection vers /dev/null(pour supprimer le message d'erreur lors de la redirection ultérieure de l'entrée de la readcommande si le fichier de compteur est manquant),
  3. dupliquer plus tard le flux d'erreurs standard d'origine (maintenant à 3) en place (2) et
  4. fermez la copie du flux d'erreur standard (3).

1

Une approche différente

Un fichier de compteur séparé présente des inconvénients:

  • Il faut 4096 octets (ou quelle que soit la taille de votre bloc) pour chaque fichier de compteur.
  • Vous devez rechercher le nom du fichier dans le script bash, puis ouvrir le fichier pour voir le nombre.
  • Il n'y a pas de verrouillage de fichier (dans les autres réponses), il est donc possible que deux personnes mettent à jour le compteur en même temps (appelé condition de concurrence dans les commentaires sous la réponse de Byte Commander).

Cette réponse supprime donc un fichier de compteur séparé et place le nombre dans le script bash lui-même!

  • Mettre le compteur dans le script bash lui-même vous permet de voir dans votre script lui-même combien de fois il a été exécuté.
  • L'utilisation flockgarantit que pendant un bref instant, il n'est pas possible pour deux utilisateurs d'exécuter le script en même temps.
  • Parce que le nom du fichier de compteur n'est pas codé en dur, vous n'avez pas besoin de changer le code pour différents scripts, vous pouvez simplement le source ou le copier et coller à partir d'un fichier stub / passe-partout.

Le code

#!/bin/bash

# NAME: run-count.sh
# PATH: $HOME/bin
# DESC: Written for AU Q&A: /ubuntu/988032/how-can-i-cause-a-script-to-log-in-a-separate-file-the-number-of-times-it-has-be

# DATE: Mar 16, 2018.

# This script run count: 0

# ======== FROM HERE DOWN CAN GO INTO FILE INCLUDED WITH SOURCE COMMAND =======

[ "${FLOCKER}" != "$0" ] && exec env FLOCKER="$0" flock -en "$0" "$0" "$@" || :
#     This is useful boilerplate code for shell scripts.  Put it at the top  of
#     the  shell script you want to lock and it'll automatically lock itself on
#     the first run.  If the env var $FLOCKER is not set to  the  shell  script
#     that  is being run, then execute flock and grab an exclusive non-blocking
#     lock (using the script itself as the lock file) before re-execing  itself
#     with  the right arguments.  It also sets the FLOCKER env var to the right
#     value so it doesn't run again.

# Read this script with entries separated newline " " into array
mapfile -t ScriptArr < "$0"

# Build search string that cannot be named
SearchStr="This script"
SearchStr=$SearchStr" run count: "

# Find our search string in array and increment count
for i in ${!ScriptArr[@]}; do
    if [[ ${ScriptArr[i]} = *"$SearchStr"* ]]; then
        OldCnt=$( echo ${ScriptArr[i]} | cut -d':' -f2 )
        NewCnt=$(( $OldCnt + 1 ))
        ScriptArr[i]=$SearchStr$NewCnt
        break
    fi
done

# Rewrite our script to disk with new run count
# BONUS: Date of script after writing will be last run time
printf "%s\n" "${ScriptArr[@]}" > "$0"

# ========= FROM HERE UP CAN GO INTO FILE INCLUDED WITH SOURCE COMMAND ========

# Now we return you to your original programming....

exit 0

Une autre approche à l'aide d'un fichier journal

Semblable à la réponse de Videonauth, j'ai écrit une réponse de fichier journal ici: Script Bash pour maintenir la piste d'audit / journal des fichiers accessibles pour se connecter chaque fois que les pouvoirs root ont été utilisés avec geditou nautilus.

Le hic est que plutôt que d'utiliser gksule script est nommé gsuet invoque pkexecla façon "moderne" d'utiliser sudo dans l'interface graphique, donc on me dit.

Un autre avantage est non seulement de dire à chaque fois que les pouvoirs root ont été utilisés, geditmais il enregistre le nom de fichier qui a été modifié. Voici le code.

~/bin/gsu:

#!/bin/bash

# Usage: gsu gedit file1 file2...
#  -OR-  gsu natuilus /dirname

# & is used to spawn process and get prompt back ASAP
# > /dev/null is used to send gtk warnings into dumpster

COMMAND="$1" # extract gedit or nautilus

pkexec "$COMMAND" "${@:2}"

log-file "${@:2}" gsu-log-file-for-"$COMMAND"

/usr/local/bin/log-file:

#! /bin/bash

# NAME: log-file
# PATH: /usr/local/bin
# DESC: Update audit trail/log file with passed parameters.
# CALL: log-file FileName LogFileName
# DATE: Created Nov 18, 2016.
# NOTE: Primarily called from ~/bin/gsu

ABSOLUTE_NAME=$(realpath "$1")
TIME_STAMP=$(date +"%D - %T")
LOG_FILE="$2"

# Does log file need to be created?
if [ ! -f "$LOG_FILE" ]; then
    touch "$LOG_FILE"
    echo "__Date__ - __Time__ - ______File Name______" >> "$LOG_FILE"
    #     MM/DD/YY - hh:mm:ss - "a/b/c/FileName"
fi

echo "$TIME_STAMP" - '"'"$ABSOLUTE_NAME"'"' >> "$LOG_FILE"

exit 0

Contenu du fichier journal gsu-log-file-for-geditaprès quelques modifications:

__Date__ - __Time__ - ______File Name______
11/18/16 - 19:07:54 - "/etc/default/grub"
11/18/16 - 19:08:34 - "/home/rick/bin/gsu"
11/18/16 - 19:09:26 - "/home/rick/bin/gsu"
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.