Formatieren und Anzeigen eines Intervalls mit Sekundenbruchteilen in der Shell

Formatieren und Anzeigen eines Intervalls mit Sekundenbruchteilen in der Shell

Ich habe ein Intervall in Sekunden mit einer Dezimalstelle, das ich in einem für Menschen lesbaren Format (H:MM:SS.SSS) anzeigen möchte. Beispielsweise sollte 16633.284 als 4:37:13.284 angezeigt werden. Alle diese Zeiten liegen unter 24 Stunden, aber wenn sie länger wären, wären es immer noch nur Stunden.

Noch ein paar Beispiele:

   0      → 0:00:00.000
  60.394  → 0:01:00.394
8944.77   → 2:29:04.770

Beachten Sie, dass die Breite für unter 10 Stunden fest ist. Natürlich ist das mit relativ einfach zu bewerkstelligen printf.

Ich poste meine Lösung als Antwort, aber ich habe das Gefühl, dass es einen besseren Weg geben muss, also frage ich: Was sind die anderen Möglichkeiten? Ich bin offen für Möglichkeiten wie Bashismen, Zshismen usw.

Hinweis: Dies bezieht sich aufSekunden als Tage/Stunden/Minuten/Sekunden anzeigen?Aber diese Ansätze funktionieren alle nicht, da die Arithmetik in Bash nur auf Ganzzahlen basiert.

Antwort1

Der Bruchteil trägt nichts zur Anzahl der Stunden, Minuten oder Sekunden bei, Sie können ihn also einfach weglassen, die Berechnungen ohne ihn durchführen und ihn am Ende wieder hinzufügen. Etwa so:

#!/bin/sh
seconds=16633.284
f=${seconds##*.}
if [ "$f" = "$seconds" ]; then f=0; fi
t=${seconds%.*}
s=$((t%60))
m=$((t/60%60))
h=$((t/60/60))
printf '%d:%02d:%06.3f\n' $h $m $s.$f

Ausgabe:

   0     → 0:00:00.000
  33.    → 0:00:33.000
    .21  → 0:00:00.210
  60.394 → 0:01:00.394
8944.77  → 2:29:04.770

Dies funktioniert in jeder sh-ähnlichen Shell (mit Ausnahme einiger alter Shells vor POSIX).

Antwort2

Mein Ansatz ist dieser. dcFür die Berechnungen verwende ich:

seconds=16633.284
printf '%i:%02i:%06.3f\n' \
    $(dc -e "$seconds d 3600 / n [ ] n d 60 / 60 % n [ ] n 60 % f")

Das printf ist hoffentlich ziemlich vertraut, aber wenn nicht, %ibedeutet es eine Ganzzahl. %02ibedeutet, die Ganzzahl mit einer führenden 0 aufzufüllen, wenn es sich um eine einstellige Zahl handelt. %06.3fist das seltsamste, aber es bedeutet 3 Ziffern nach dem Dezimalpunkt und das Auffüllen mit führenden 0en, wenn die Gesamtlänge weniger als 6 beträgt (einschließlich des Dezimalpunkts).

Die absichtlich nicht in Anführungszeichen gesetzte $(…)Ersetzung ergibt diese drei Parameter unter Verwendung von dc:

Schieben Sie zunächst $seconds(erweitert, also die tatsächliche Zahl) auf den Stapel; duplizieren Sie es; dividieren Sie es dann (mit Truncate) durch 3600, um die Stunden zu erhalten. Popen Sie das und drucken Sie es aus, drücken Sie dann die Leertaste und drucken Sie es aus ( [ ] n).

Damit bleiben nur die Sekunden auf dem Stapel. Duplizieren Sie es und dividieren Sie es durch 60 (erneut kürzen). Das ergibt Minuten; nehmen Sie es mod 60, um die Stunden wegzulassen. Drucken Sie es aus, wieder mit dem Leerzeichen. Damit bleiben nur die Sekunden auf dem Stapel.

Schließlich gibt Mod 60 nur das Sekundenfeld zurück – und in dc kommt Modulus mit Dezimalstellen vollkommen zurecht und behält die Genauigkeit bei. fAnschließend wird der Stapel plus eine neue Zeile gedruckt.

Antwort3

Sie können es ganz einfach in einfachem sh tun. Sie können den Code mit GNU date etwas kürzer machen (ich hoffe, es macht Ihnen nichts aus, die Millisekunden abzurunden, das Runden auf die nächste Zahl würde etwas länger dauern):

hours=$((${seconds%.*} / 3600))
min_s_nano=$(TZ=GMT date -d @$seconds +%M:%S.%N)
time_string=$hours:${min_s_nano%??????}

Antwort4

Wir rufen dcdie Anzahl der Sekunden auf, legen sie auf dem Stapel ab und führen die Berechnungen (Sek. -> HH/MM/SS.MSEC), die Formatierung und die Anzeige der Ergebnisse durch.

seconds=16633.284

dc <<DC
# rearrange stack for number < 1hr
[r ldx q]sb

# rearrange stack for number < 1min
[rd ldxq]sc

# min++, sec-=60   hrs++, min-=60
[r1+r 60-d 60!>a]sa

# display result as h:mm:ss.sss
[n 58an 2lpx 58an 2lpx 46an 3lpx 10an]sd

# perform the conversion of seconds -> hours, minutes, seconds
[d60 >c lax r0rd60 >b lax r ldx]si

[
d1000* 1000% 1/r   # fractional portion
 1000* 1000/ 1/0r  # integer    portion
lix
]sh

# left zero-pad a number
[lk1+d sk 0n le >g ]sg
[sedZd sk    le >gn]sp

# setup the number on the stack and begin computation
$seconds lhx
DC

Ergebnisse

0                 -->  0:00:00.000
60.394            -->  0:01:00.394
8944.77           -->  2:29:04.770
86399.99          -->  23:59:59.990
59.9999           -->  0:00:59.999
599.9999          -->  0:09:59.999
16633.284         -->  4:37:13.284
33                -->  0:00:33.000
.21               -->  0:00:00.210
123456789.123456  -->  34293:33:09.123

verwandte Informationen