Comment avoir un historique des commandes avec des horodatages en sortie sur le terminal en continu?

9

J'utilise un simple alias pour permettre le "tracking" des commandes dans une ou plusieurs fenêtre (s) de terminal:

alias trackmi='export PROMPT_COMMAND="history -a; $PROMPT_COMMAND"'

Ensuite, je viens de tail -fmon fichier .bash_history dans un autre terminal sur l'espace de travail pour obtenir un retour immédiat. Je viens d'activer un historique illimité et de mettre à jour mon format d'historique ( export HISTTIMEFORMAT="[%F %T] ") en .bashrc . Bien sûr, la historycommande affiche les horodatages. Mais le format du fichier historique en soi est:

#1401234303
alias
#1401234486
cat ../.bashrc 

Comment puis-je convertir le temps Unix et afficher l'intégralité de la commande sur une seule ligne, comme avec la historycommande, y compris la numérotation:

578  [2014-05-27 19:45:03] alias
579  [2014-05-27 19:48:06] cat ../.bashrc 

... et suivez ça. Ou trouver un moyen de sortir en continu la sortie de la historycommande vers le terminal?

Communauté
la source

Réponses:

7

Avec GNU awk:

tail -fn+1 ~/.bash_history | awk '
  /^#/{printf "%-4d [%s] ", ++n, strftime("%F %T", substr($0, 2)); next}; 1'
Stéphane Chazelas
la source
Fonctionne très bien et s'affiche initialement plus rapidement si je mets à jour l'autre solution avec fn+1pour comparer! Merci!
5

Voici le produit final en action sur un xterm à écran partagé, des fonctions par défaut du shell à l'utilisation de quelques commandes:

entrez la description de l'image ici

Une façon plus grossière de le faire que celle illustrée dans la capture d'écran pourrait ressembler à ceci:

PS1='$( { date ; fc -l -0 ; } >${TGT_PTY} )'$PS1

${TGT_PTY}serait tout ce que vous retirez de la ttycommande lorsque vous exécutez réellement un shell interactif sur l'écran où vous voulez votre sortie. Ou, vraiment, vous pouvez utiliser n'importe quel fichier inscriptible car il s'agit essentiellement d'une cible pour la redirection de fichiers.

J'utilise la syntaxe pty pour le pseudo-terminal parce que je suppose que c'est un xterm d'une certaine sorte, mais vous pourriez tout aussi facilement dédier un vt - et votre historique en streaming n'est toujours qu'à une CTRL-ALT-Fncombinaison de touches. Si c'était moi je pourrais combiner les deux notions et en faire une screenou tmuxsession sur un vt dédié ... Mais je m'égare.

Sur une machine fraîchement démarrée, je suis accueilli par l' /bin/logininvite typique d'une gettyconsole Linux typique . J'appuie CTRL-ALT-F2pour accéder à une kmsconconsole moins typique qui se comporte beaucoup plus comme un xtermque comme un tty. J'entre la commande ttyet reçois en réponse /dev/pts/0.

En général, xterms multiplexe un seul terminal en plusieurs en utilisant des pseudo-terminaux - donc si vous deviez faire une chose similaire dans X11 en basculant entre les onglets de terminal ou les fenêtres, vous recevriez probablement une sortie /dev/pts/[0-9]*également. Mais les consoles de terminaux virtuels accessibles avec CTRL-ALT-Fndes combinaisons de touches sont de véritables (terminaux) terminaux et reçoivent donc leur propre /dev/tty[0-9]*désignation.

C'est pourquoi après la connexion à la console 2 lorsque je tape ttyà l'invite, la réponse est, /dev/pts/0mais lorsque je fais de même sur la console 1, la sortie est /dev/tty1. En tout cas, de retour sur la console 2 je fais alors:

bash
PS1='$( { date ; fc -l -0 ; } >/dev/tty1 )'$PS1

Il n'y a aucun effet perceptible. Je continue à taper quelques commandes supplémentaires, puis je passe à la console 1 en appuyant à CTRL-ALT-F1nouveau. Et là, je trouve des entrées répétées qui ressemblent à <date_time>\n<hist#>\t<hist_cmd_string>chaque commande que j'ai tapée sur la console 2.

À moins d'écrire directement sur un terminal, une autre option pourrait ressembler à ceci:

TGT_PTY=
mkfifo ${TGT_PTY:=/tmp/shell.history.pipe}
{   echo 'OPENED ON:'
    date
} >${TGT_PTY}

Et puis peut-être ...

less +F ${TGT_PTY}

La commande d'invite approximative ne correspond pas à vos spécifications - pas de chaîne de dateformatage ni d'options de formatage pour les fcdeux - mais son mécanisme ne nécessite pas beaucoup: chaque fois que votre invite rend la dernière commande d'historique et la date et l'heure actuelles sont écrites dans le ${TGT_PTY}fichier que vous spécifiez. C'est aussi simple que ça.

L'observation et l'impression de l'historique de la coque est fcl'objectif principal de. C'est un shell intégré, même s'il datene l'est pas. Dans zsh fcpeut fournir toutes sortes d'options de mise en forme fantaisie, dont plusieurs s'appliquent aux horodatages. Et bien sûr, comme vous le constatez ci - dessus, bashest historypeut faire la même chose.

Dans l'intérêt d'une sortie plus propre, vous pouvez utiliser une technique que j'ai mieux expliquée ici pour définir une variable de suivi persistante dans le shell actuel , même si vous devez la suivre et la traiter en sous-coquilles dans la séquence d'invite.

Voici un moyen portable de formatage selon vos spécifications:

_HIST() { [ -z ${_LH#$1} ] ||
    { date "+${1}%t[%F %T]"
      fc -nl -0 
    } >${TGT_PTY}
    printf "(_LH=$1)-$1"
}

: "${_LH=0}"
PS1='${_LH##*[$(($(_HIST \!)))-9]}'$PS1

Je Mettre en oeuvre le last_history contre $_LHque les pistes seulement les dernières mises à jour afin que vous n'écrivez pas la même commande l' histoire deux fois - par exemple juste pour entrer en appuyant sur. Il y a un peu de dispute nécessaire pour obtenir la variable incrémentée dans le shell actuel afin qu'elle conserve sa valeur même si la fonction est appelée dans un sous-shell - ce qui est, encore une fois, mieux expliqué dans le lien .

Sa sortie ressemble à <hist#>\t[%F %T]\t<hist_cmd>\n

Mais ce n'est que la version entièrement portable. Avec bashcela, vous pouvez le faire avec moins et en implémentant uniquement des commandes internes de shell - ce qui est probablement souhaitable lorsque vous considérez que c'est une commande qui s'exécutera à chaque fois que vous appuyez sur [ENTER]. Voici deux façons:

_HIST() { [ -z ${_LH#$1} ] || {
        printf "${1}\t[%(%F %T)T]"
        fc -nl -0
    } >${TGT_PTY}
    printf "(_LH=$1)-$1"
}
PROMPT_COMMAND=': ${_LH=0};'$PROMPT_COMMAND
PS1='${_LH##*[$(($(_HIST \!)))-9]}'$PS1

Alternativement, en utilisant bashla historycommande de, vous pouvez définir la _HISTfonction de cette façon:

_HIST() { [ -z ${_LH#$1} ] || 
        HISTTIMEFORMAT="[%F %T]<tab>" \
        history 1 >${TGT_PTY}
    printf "(_LH=$1)-$1"
}

La sortie de l'une ou l'autre méthode ressemble également à ceci: <hist#>\t[%F %T]\t<hist_cmd>\nbien que la historyméthode comprenne des espaces de début. Néanmoins, je pense que les historyhorodatages de la méthode seront plus précis car je ne pense pas qu'ils devraient attendre que la commande référencée se termine avant d'acquérir leur tampon.

Vous pouvez éviter de suivre n'importe quel état dans les deux cas si seulement vous filtrez le flux avec uniq- comme vous le feriez avec mkfifocomme je l'ai mentionné précédemment.

Mais le faire dans l'invite comme ceci signifie qu'il est toujours mis à jour uniquement dès que cela est nécessaire par la simple action de mettre à jour l'invite. C'est simple.

Vous pouvez également faire quelque chose de similaire à ce que vous faites, tailmais plutôt définir

HISTFILE=${TGT_PTY}
mikeserv
la source
Je suis en train de le modifier dans un autre onglet ... Plus de temps s'il vous plaît?
mikeserv
Eh bien, je vais sauvegarder en ce moment en fait, @ illuminÉ - mais vous verrez où je me suis arrêté ...
mikeserv
@ illuminÉ - Soit dit en passant - et j'espère que j'ai raison à ce sujet - je suppose que vous avez entré la commande telle qu'elle est écrite ${TGT_PTY}et tout? Si c'est le cas, cela expliquerait la «redirection ambiguë» car ce serait une variable vide. Vous avez besoin d'un fichier. /dev/pts/[num]selon toute vraisemblance -
mikeserv
Ça marche! L'écran d'impression a aidé! Je souhaite que votre premier et unique bloc de code soit ce que vous mettez dans l'écran d'impression et une référence claire pour choisir les points que vous souhaitez que la sortie aille et entrer l'onglet dans la fonction - rien d'autre n'est requis. L'utilisation d'une variable pour décrire quelque chose que j'ai dû saisir manuellement pour l'essayer rapidement n'est pas une bonne pratique à mon avis car vous devez creuser dans le texte pour tout comprendre, ce qui obscurcit la solution. Aussi tout le meilleur pour vous et votre famille.
@ illuminÉ - peu importe que catj'étais paranoïaque. Cela fonctionne bien - même 12 heures plus tard.
mikeserv
4

N'hésitez pas à jouer avec le formatage, mais cela fait (je crois) ce que vous demandez ... enregistrez quelque part dans votre CHEMIN, rendez exécutable et profitez:

#!/bin/bash
count=$(  echo "scale=0 ; $(cat ~/.bash_history | wc -l ) / 2" | bc -l )
tail -f ~/.bash_history | awk -v c=$count '{if($1 ~/^#/){gsub(/#/, "", $1);printf "%s\t", c; "date \"+%F %T\" --date @" $1 | getline stamp; printf "[%s]\t",stamp;c++}else{print $0}}'

Je suis sûr qu'il peut être optimisé, mais vous avez compris.

Brève explication: comme ~ / .bash_history ne garde pas la trace du décompte, nous déterminons d'abord le nombre d'entrées. Ensuite, un peu de magie awk pour obtenir la bonne mise en forme et garder une trace du nombre d'entrées.

tink
la source
Cela ne fonctionne pas s'il y a des entrées sur plusieurs lignes. Lira également tail -f10 lignes initialement qui ont déjà été incluses dans votre count. Il suppose la date GNU dans un environnement non-POSIX (POSIXLY_CORRECT non défini). Il exécute un shell et une commande date par horodatage.
Stéphane Chazelas
@StephaneChazelas Pour les entrées multilignes, sur ma configuration, elles semblent s'inscrire avec les deux solutions. Quelque chose dans ma configuration peut-être?
1
@ illuminÉ, tink's countcompte la moitié de la ligne .bash_history, puis incrémente pour chaque ligne qui ne commence pas# , de sorte que le nombre d'historique rapporté est probablement incorrect. L'utilisation count=$(grep -c '^#' ...)serait probablement meilleure, mais dans tous les cas, ces numéros d'historique risquent de ne pas être synchronisés, surtout si vous avez plus de 2 bash en cours d'exécution en même temps.
Stéphane Chazelas
@StephaneChazelas Cordialement car je ne peux pas apprécier pleinement l'une ou l'autre solution Je suis très reconnaissant pour l'explication! En effet, le nombre est différent, beaucoup plus élevé par conséquent ici ... Je peux voir que vous avez construit votre propre solution autour de strftime, qui est essentiellement ce que la historycommande exploite.
1
Merci pour le feed-back @StephaneChazelas, je vais voir si je peux contourner ces :)
tink