Ik wil een tijdstempel toevoegen aan elke uitvoerregel van een commando. Bijvoorbeeld:
foo bar baz
zou worden
[2011-12-13 12:20:38] foo [2011-12-13 12:21:32] bar [2011-12-13 12:22:20] baz
… waar de tijd is voorafgegaan is het tijdstip waarop de regel werd afgedrukt. Hoe kan ik dit bereiken?
Opmerkingen
- Is er een Unix-hulpprogramma om tijdstempels toe te voegen aan stdin?
Antwoord
moreutils bevat ts
wat dit heel goed doet:
command | ts "[%Y-%m-%d %H:%M:%S]"
Het elimineert ook de noodzaak van een lus, elke regel met uitvoer zal een tijdstempel hebben.
$ echo -e "foo\nbar\nbaz" | ts "[%Y-%m-%d %H:%M:%S]" [2011-12-13 22:07:03] foo [2011-12-13 22:07:03] bar [2011-12-13 22:07:03] baz
Wil je weten wanneer die server terugkwam en herstartte? Voer gewoon ping | ts
uit, probleem opgelost: D.
Opmerkingen
- Hoe weet ik dit niet ?!?!?! Dit vult staart -f verbazingwekkend aan!
tail -f /tmp/script.results.txt | ts
- als ik ‘ geen ts commando heb, wat moet ik dan gebruiken?
- Als het ‘ niet werkt, probeer dan stderr om te leiden naar stdout, bijv.
ssh -v 127.0.0.1 2>&1 | ts
- Installeer door
sudo apt install moreutils
op Debian enyum install moreutils
op Fedora. - Ik denk dat het nuttig is om naar de parameter
-s
te verwijzen. Omdat dat de looptijd van de opdracht weergeeft. Persoonlijk vind ik het leuk omts
ents -s
tegelijkertijd te gebruiken. Ziet er ongeveer zo uit:command | ts -s '(%H:%M:%.S)]' | ts '[%Y-%m-%d %H:%M:%S'
. Dit begint als volgt aan de logregels:[2018-12-04 08:31:00 (00:26:28.267126)] Hai <3
Antwoord
Ten eerste, als u verwacht dat deze tijdstempels daadwerkelijk een gebeurtenis vertegenwoordigen, houd er dan rekening mee dat aangezien veel programmas lijnbuffering uitvoeren (sommige agressiever dan andere), het belangrijk is om dit zo dicht mogelijk bij de tijd te zien waarop de oorspronkelijke regel zou zijn afgedrukt in plaats van een tijdstempel van een actie die plaatsvindt.
U kunt ook controleren of uw commando niet al een ingebouwde functie heeft die hiervoor is bedoeld. Als voorbeeld: ping -D
bestaat in sommige ping
versies, en drukt de tijd sinds het Unix-epoch af voor elke regel. Als uw commando echter geen eigen methode bevat, is er zijn enkele methoden en tools die kunnen worden gebruikt, onder andere:
POSIX shell
Houd in gedachten dat aangezien veel shells hun strings intern opslaan als cstrings, als de invoer de null char acter (\0
), kan dit ervoor zorgen dat de regel voortijdig eindigt.
command | while IFS= read -r line; do printf "[%s] %s\n" "$(date "+%Y-%m-%d %H:%M:%S")" "$line"; done
GNU awk
command | gawk "{ print strftime("[%Y-%m-%d %H:%M:%S]"), $0 }"
Perl
command | perl -pe "use POSIX strftime; print strftime "[%Y-%m-%d %H:%M:%S] ", localtime"
Python
command | python -c "import sys,time;sys.stdout.write("".join(( " ".join((time.strftime("[%Y-%m-%d %H:%M:%S]", time.localtime()), line)) for line in sys.stdin )))"
Ruby
command | ruby -pe "print Time.now.strftime("[%Y-%m-%d %H:%M:%S] ")"
Reacties
- Een probleem hier is dat veel programmas worden ingeschakeld zelfs meer outputbuffering als hun stdout een pipe is in plaats van de terminal.
- @cjm – True. Sommige uitvoerbuffering kan worden verlicht door
stdbuf -o 0
te gebruiken, maar als het programma de uitvoerbuffering handmatig afhandelt, krijgt het ‘ t hulp (tenzij er is een optie om de grootte van de uitvoerbuffer uit te schakelen / te verkleinen). - Voor python kun je lijnbuffering uitschakelen met
python -u
- @Bwmat No.
... for x in sys.stdin
itereert over regels zonder ze eerst allemaal in het geheugen te bufferen. - Doe dit en je krijgt buffering … voor een in 1 1 1 1 1; slaap 1; echo; gedaan | python -c ‘ import sys, time; sys.stdout.write (” ” .join ((” ” .join ((time.strftime (” [% Y-% m-% d% H:% M:% S] “, time.gmtime ()), line)) voor regel in sys.stdin))) ‘
Antwoord
Voor een regel-voor-regel deltameting , probeer gnomon .
Het is een opdrachtregelprogramma, een beetje zoals moreutils “s ts, om tijdstempelinformatie toe te voegen aan de standaarduitvoer van een ander commando. Nuttig voor langlopende processen waarbij” je een historisch record wilt “van wat er zo lang duurt.
Alles naar gnomon sturen voeg een tijdstempel toe aan elke regel om aan te geven hoe lang die regel de laatste regel in de buffer was, dat wil zeggen, hoe lang het duurde voordat de volgende regel werd weergegeven. Standaard geeft gnomon de verstreken seconden weer tussen elke regel, maar dat is configureerbaar.
Reacties
- Ziet eruit als een geweldig alternatief voor
ts
bij het gebruik van live processen. Hoewelts
beter geschikt is voor niet-interactieve processen.
Antwoord
Ik had liever hierboven commentaar gegeven, maar dat kan ik niet, reputatieschijnlijk. Hoe dan ook, het bovenstaande Perl-voorbeeld kan als volgt worden ongebufferd:
command | perl -pe "use POSIX strftime; $|=1; select((select(STDERR), $| = 1)[0]); print strftime "[%Y-%m-%d %H:%M:%S] ", localtime"
De eerste “$ |” ontbuffert STDOUT. De tweede stelt stderr in als het huidige standaard uitvoerkanaal en ontbuffert het. Aangezien select de oorspronkelijke instelling van $ | retourneert, door de select in een select te wikkelen, zetten we ook $ | terug naar de standaard, STDOUT.
En ja, je kunt knippen en plakken zoals het is. Ik heb het meerdere lijnen gegeven voor leesbaarheid.
En als je echt precies wilt zijn (en je hebt Time :: Hires geïnstalleerd):
command | perl -pe "use POSIX strftime; use Time::HiRes gettimeofday; $|=1; select((select(STDERR), $| = 1)[0]); ($s,$ms)=gettimeofday(); $ms=substr(q(000000) . $ms,-6); print strftime "[%Y-%m-%d %H:%M:%S.$ms]", localtime($s)"
Reacties
- Werkt als een charme, zonder dat je niet-standaard pakketten hoeft te installeren.
Antwoord
Ryans bericht geeft een interessant idee, maar het faalt in verschillende opzichten. Tijdens het testen met tail -f /var/log/syslog | xargs -L 1 echo $(date +"[%Y-%m-%d %H:%M:%S]") $1
, merkte ik dat het tijdstempel hetzelfde blijft, zelfs als stdout
komt later met verschil in seconden uit elkaar. Beschouw deze uitvoer:
[2016-07-14 01:44:25] Jul 14 01:44:32 eagle dhclient[16091]: DHCPREQUEST of 192.168.0.78 on wlan7 to 255.255.255.255 port 67 (xid=0x411b8c21) [2016-07-14 01:44:25] Jul 14 01:44:34 eagle avahi-daemon[740]: Joining mDNS multicast group on interface wlan7.IPv6 with address fe80::d253:49ff:fe3d:53fd. [2016-07-14 01:44:25] Jul 14 01:44:34 eagle avahi-daemon[740]: New relevant interface wlan7.IPv6 for mDNS.
Mijn voorgestelde oplossing is vergelijkbaar, maar biedt de juiste tijdstempel en gebruikt iets meer draagbare printf
in plaats van echo
| xargs -L 1 bash -c "printf "[%s] %s\n" "$(date +%Y-%m-%d\ %H:%M:%S )" "$*" " bash
Waarom bash -c "..." bash
? Omdat vanwege de -c
optie, eerst argumenteren nt wordt toegewezen aan $0
en wordt “niet weergegeven in de uitvoer. Raadpleeg de handleidingpagina van je shell voor de juiste beschrijving van -c
Deze oplossing testen met tail -f /var/log/syslog
en (als je zou waarschijnlijk kunnen raden) het verbreken en opnieuw verbinden met mijn wifi, heeft de juiste tijdstempel getoond die wordt geboden door zowel date
als syslog
berichten
Bash kan worden vervangen door elke bourne-achtige shell, kan worden gedaan met ksh
of dash
, tenminste die die een -c
optie hebben.
Potentiële problemen:
De oplossing vereist xargs
, die beschikbaar is op POSIX-compatibele systemen, dus de meeste Unix-achtige systemen moeten worden gedekt. Het zal duidelijk “niet werken als uw systeem niet-POSIX-compatibel is of geen GNU findutils
Antwoord
De meeste antwoorden suggereren om date
te gebruiken, maar het is traag genoeg . Als je bash-versie groter is dan 4.2.0, is het beter om printf
te gebruiken, het is een ingebouwde bash. Als je oudere bash-versies wilt ondersteunen, kun je een log
-functie maken, afhankelijk van de bash-versie:
TIMESTAMP_FORMAT="%Y-%m-%dT%H:%M:%S" # Bash version in numbers like 4003046, where 4 is major version, 003 is minor, 046 is subminor. printf -v BV "%d%03d%03d" ${BASH_VERSINFO[0]} ${BASH_VERSINFO[1]} ${BASH_VERSINFO[2]} if ((BV > 4002000)); then log() { ## Fast (builtin) but sec is min sample for most implementations printf "%(${TIMESTAMP_FORMAT})T %5d %s\n" "-1" $$ "$*" # %b convert escapes, %s print as is } else log() { ## Slow (subshell, date) but support nanoseconds and legacy bash versions echo "$(date +"${TIMESTAMP_FORMAT}") $$ $*" } fi
Zie snelheid verschillen:
user@host:~$time for i in {1..10000}; do printf "%(${TIMESTAMP_FORMAT})T %s\n" "-1" "Some text" >/dev/null; done real 0m0.410s user 0m0.272s sys 0m0.096s user@host:~$time for i in {1..10000}; do echo "$(date +"${TIMESTAMP_FORMAT}") Some text" >/dev/null; done real 0m27.377s user 0m1.404s sys 0m5.432s
UPD: in plaats van $(date +"${TIMESTAMP_FORMAT}")
is het beter om of zelfs $(exec -c date +"${TIMESTAMP_FORMAT}")
te versnellen uitvoering.
UPD2: bash 5 biedt EPOCHREALTIME
variabele voor microseconden granulariteit, je kunt het gebruiken met dit commando (ongeveer 30% langzamer dan alleen seconden): printf "%(${TIMESTAMP_FORMAT})T.%s %5d %s\n" ${EPOCHREALTIME/./ } $$ "$*"
Reacties
- Dit antwoord is erg onderschat! Bijzonder mooi om de relatieve prestatie-impact te laten zien.
- Uitstekende suggestie, maar helaas biedt date geen sub-seconde resolutie, wat in mijn geval een vereiste is
- Met bash 5.0+ kun je dit commando gebruiken:
printf "%(${TIMESTAMP_FORMAT})T.%s %5d %s\n" ${EPOCHREALTIME/./ } $$ "$*"
Antwoord
U kunt dit doen met date
en xargs
:
... | xargs -L 1 echo `date +"[%Y-%m-%d %H:%M:%S]"` $1
Uitleg:
xargs -L 1
vertelt xargs om het volgende commando uit te voeren voor elke 1 regel invoer, en het gaat in de eerste regel door terwijl het dit doet. echo `date +"[%Y-%m-%d %H:%M:%S]"` $1
echoot in feite de datum met het invoerargument aan het einde ervan
Opmerkingen
- De oplossing is dichtbij, maar ‘ t tijdstempel correct als het gaat om uitvoer gescheiden door lange tijdsperioden. Je ‘ gebruikt ook backticks en hebt ‘ niet
$1
geciteerd. Dat ‘ is geen goede stijl. Geef altijd de variabelen aan. Bovendien ‘ gebruikt uecho
, wat niet draagbaar is. Het ‘ is in orde, maar werkt mogelijk niet goed op sommige systemen. - Nadat je dit hebt getest, lijkt het erop dat je ‘ helemaal gelijk hebt … weet je een manier om
date
elke regel opnieuw worden geëvalueerd, of is het vrijwel hopeloos?
Antwoord
Schaamteloze plug voor iets dat ik schreef net om dit exacte probleem op te lossen: ets
, geschreven in Go.
Je kunt veel gebruiksvoorbeelden vinden op de projectpagina.
Het opmerkelijke verschil met bestaande antwoorden en soortgelijke aanbiedingen is dat ets
is ontworpen om uw opdracht voor u uit te voeren, in een pty (pseudo tty) – dat wil zeggen, simuleren uw commando wordt native uitgevoerd in een tty. Vergeleken met de uitvoer van een pipingcommando naar bijv. ts
, dit maakt tijdstempels meestal transparant, en lost een aantal problemen met piping op:
- Sommige programmas bufferen agressief wanneer ze naar een pipe schrijven, dus je zie geen output en dan een heleboel output (ja, je kunt ze stdbufen, je kunt zelfs stdbuf en ts in een alias / functie verpakken, maar het zou niet beter zijn als de dingen uit de doos werken);
- Sommige programmas schakelen kleur en / of interactiviteit uit bij het schrijven naar een pipe;
- De exitstatus is verdwenen tenzij je pipefail hebt ingeschakeld; etc.
Commandos kunnen direct worden uitgevoerd, wat inhoudt dat u ets
eenvoudig kunt toevoegen aan uw bestaande opdrachtregel, of het kunnen shell-opdrachten zijn (zoals weergegeven in het bovenstaande gif). Natuurlijk, als je de uitvoer wilt doorsturen, ets
kan dat ook doen.
ets
ondersteunt hetzelfde tijdstempelmodi als moreutils ts
: absolute tijdmodus, verstreken tijdmodus en incrementele tijdmodus. Het gebruikt betere standaardinstellingen (bijv. Monotone klok wordt altijd gebruikt voor verstreken / incrementele tijdstempels) en heeft extra ondersteuning voor aangepaste tijdzones. Er “een gedetailleerde vergelijking hier .
Nogmaals, https://github.com/zmwangx/ets . Geef het een draai, rapporteer bugs, etc.
Antwoord
Zet dit bij het begin van uw script
# prepend a timestamp to all output exec &> >( ts "%Y-%m-%d.%H%M.%.S " )
Of, voor extra krediet, uitvoer naar logbestand via:
script_name=foobar log_file=$( printf "/tmp/${script_name}-%(%Y-%m-%d)T.%(%H%M%S)T.log" -1 ) echo "note: redirecting output to [${log_file}]" exec &> >( ts "%Y-%m-%d %H:%M:%.S " > ${log_file} )
om uit te voeren naar zowel de console als het logbestand:
script_name=foobar log_file=$( printf "/tmp/${script_name}-%(%Y-%m-%d)T.%(%H%M%S)T.log" -1 ) exec &> >( ts "%Y-%m-%d %H:%M:%.S " | tee ${log_file} )
De belangrijkste voordelen hiervan zijn om het loggen te scheiden van al het andere, niet de body van het script onoverzichtelijk maken door naar tee
of iets dergelijks te pipen bij elke opdracht, en geen aangepaste logboekfuncties te hoeven schrijven en vast te houden aan gewoon oud echo
.
Het ts
programma is te vinden in het moreutils
pakket, dat direct beschikbaar zou moeten zijn onder elk redelijkerwijs gezond administratief regime. 🙂