
Ich habe einige Daten, die über Zeitintervalle verteilt sind, und ich möchte einige dieser Daten innerhalb der Zeitintervalle erfassen. Beispielsweise habe ich Daten zu Zeitpunkten zwischen 1..9, 11..19 usw. und ich möchte Daten zwischen 1-2, dann zwischen 11-12 usw. erfassen.
Dies wird Teil eines komplexeren bash
Skripts sein, in das ich diese Bedingung mit einem Zyklus einbinden möchte if
, um die Zeiten zu isolieren, in denen ich die Daten erfassen kann.
Ich dachte an so etwas wie:
if (( $t_initial & $t_final )) in (begin1, fin1) or in (begin2, fin2) ...
wobei t_initial
und t_final
separat vom Skript selbst berechnet werden.
Ich kann diese Bedingung nicht in bash
Syntax schreiben. Ich habe einige andere Lösungen gefunden, aber sie scheinen extrem lang und unpraktisch zu sein, deshalb bin ich hier, um nach einfacheren und lesbareren Lösungen zu fragen.
Es ist wichtig, dass der Code mit Floats richtig funktioniert. Ich versuche, die OP-Lösung dafür zu reparieren, finde aber immer noch keinen Weg.
Antwort1
Ich bin nicht sicher, ob Ihnen diese Lösung gefällt. Sie hat zwei Funktionen:
- Es sind keine externen Programme erforderlich
- Es verwendet eine Funktion, so dass zumindest die Komplexität der Vergleiche verborgen bleibt
Das ist es:
#!/bin/bash
# The comparing function
function compareInterval {
t1=$1
t2=$2
shift 2
while (( "$2" )); do
if (( $t1 >= $1 && $t2 <= $2 )); then
# got match
return 0
fi
shift 2
done
return 1
}
# sample values
t_initial=2
t_final=4
# Invocation. Compares against 1-3, 3-5, 2-5
if compareInterval $t_initial $t_final 1 3 3 5 2 5; then
echo Got match
fi
Antwort2
bash
unterstützt weder einen Bereichsvergleich noch Gleitkommazahlen von Haus aus, daher müssen wir einiges davon selbst erledigen. Ich werde auch eine Funktion definieren und sie bc
für Gleitkommaberechnungen verwenden. Hier ist das Endergebnis und die Testsuite:
# Call as `compareRanges start end b1 f1 b2 f2 b3 f3...`
compareRanges() {
local t_initial=$1
local t_final=$2
shift 2
while [ ${#@} -gt 1 ]
do
local in_range=$(bc <<<"$t_initial >= $1 && $t_final <= $2")
if [ $in_range = 1 ]
then
# Debugging output to stderr - can be removed:
echo "[$t_initial,$t_final] is within [$1,$2]" >&2
return 0
fi
shift 2
done
# Debugging output to stderr - can be removed:
echo "[$t_initial,$t_final] is not within any ranges." >&2
return 1
}
# Basic integers from the example
compareRanges 1 3 2 4 && echo BAD || echo OK
compareRanges 1 3 1 3 && echo OK || echo BAD
compareRanges 1 3 0 4 && echo OK || echo BAD
# Fractional numbers
compareRanges 1.5 2.5 1.1 2.2 && echo BAD || echo OK
compareRanges 1.5 2.5 0.3 3.1 && echo OK || echo BAD
# Multiple ranges
compareRanges 5 7 1 4 2 6 3 9 && echo OK || echo BAD
compareRanges 5 7 1 2 3 4 5 6 7 8 && echo BAD || echo OK
Die compareRanges
Funktion nimmt mindestens zwei Argumente an. Das erste ist Ihr t_initial
, und das zweite ist Ihr t_final
. Danach kann sie beliebig viele andere Argumente paarweise annehmen, und zwar in der Reihenfolge Ihre begin1
, fin1
, begin2
, fin2
, .
Der erste Testfall vergleicht die Bereiche in den Kommentaren zur Frage: 1-3 und 2-4.
compareRanges 1 3 2 4 && echo BAD || echo OK
So 1
ist t_initial
, 3
ist t_final
, 2
ist begin1
und 4
ist fin1
.
Wenn Sie mehrere Bereiche verwenden möchten, listen Sie diese anschließend alle paarweise auf:
compareRanges 5 7 1 4 2 6 3 9 && echo OK || echo BAD
Hier testen wir gegen 1-4, 2-6 und 3-9. In der while
Schleife betrachten wir jedes Paar der Reihe nach und vergleichen es mit t_initial
und t_final
.
Da bash
keine Bruchzahlen unterstützt werden, verwenden wirbc
, ein Rechner mit beliebiger Genauigkeit. Seine Eingabe wird durch den <<<"$t_initial >= $1" ...
Teil bereitgestellt: der den String in die Standardeingabe einspeist. $1
ist der Anfang des Bereichs, den wir uns gerade in dieser Iteration der Schleife ansehen, und $2
ist das Ende; wir vergleichen sowohl die Unter- als auch die Obergrenze gleichzeitig mit &&
. bc
gibt aus, 1
wenn die Vergleiche wahr sind, und 0
wenn einer falsch ist. Wir speichern das Ergebnis in in_range
, und die Funktion ist erfolgreich ( return 0
), wenn beide unserer Tests wahr waren.
Bruchzahlen können einfach in ihrer normalen Dezimalform angegeben werden:
compareRanges 1.5 2.5 0.3 3.1 && echo OK || echo BAD
bc
verarbeitet Zahlen mit beliebig vielen Nachkommastellen und in der von Ihnen benötigten Größenordnung.
Wenn am Ende keines der Grenzpaare übereinstimmt, schlagen wir fehl ( return 1
). Sie können die Funktion wie folgt verwenden:
if compareRanges $t_initial $t_final 2 4 11 19
then
...
fi
Die Testsuite sollte beim Ausführen ausschließlich „OK“ ausgeben.
Alternativ können auch andere Schalen (wiezsh
)Tununterstützt gebrochene Variablenwerte. Wenn Sie Ihr Skript in einem dieser Formate ausführen könnten, könnten Sie die Verwendung von vermeiden bc
, obwohl der Vergleich in einer Funktion immer noch besser ist. Zumindest in zsh
diesem Fall handelt es sich um Gleitkommazahlen, sodass sie nicht unbedingt genau sind; bc
wird immer korrekt sein.
Antwort3
Hier sind ein paar schamlose Nachahmungen von LatinSuDs Antwort, die mit Gleitkommazahlen umgehen. Sie werden bemerken, dass seine Antwort damit prahlt: „Es sind keine externen Programme erforderlich.“ Diese hier verwendet das Taschenrechnerprogramm, bc
wie von ihm vorgeschlagen:
#!/bin/bash
# The comparing function
function compareInterval {
t1=$1
t2=$2
shift 2
while (( "$2" ))
do
# if (( $t1 >= $1 && $t2 <= $2 ))
bc_result=$(echo "print $t1 >= $1 && $t2 <= $2" | bc)
if [ "$bc_result" = 1 ]
then
# got match
return 0
fi
shift 2
done
return 1
}
# sample values
t_initial=2.3
t_final=4.2
# Invocation. Compares against 1-3, 3-5, 2-5
if compareInterval $t_initial $t_final 1 3 3 5 2 5
then
echo Got match
fi
Dadurch wird der Test einfach übernommen if (( $t1 >= $1 && $t2 <= $2 ))
und an gesendet bc
. Anschließend wird die Ausgabe von erfasst bc
.
Ein anderer Ansatz besteht darin, die Zahlen durch Multiplikation mit einer Zehnerpotenz auf Ganzzahlen zu normalisieren. Dies erfordert eine maximale Anzahl von Dezimalstellen. Wenn beispielsweise kein Datenpunkt mehr als drei Ziffern rechts vom Dezimalpunkt hat, können wir alles mit 1000 multiplizieren.
#!/bin/bash
# Normalize function: it multiplies a floating point number by 1000
# without using floating point arithmetic.
normalize()
{
case "$1" in
*.*)
result=$(echo "$1"000 | sed 's/\(.*\)\.\(...\).*/\1\2/')
;;
*)
result="$1"000
esac
echo "$result"
}
# The comparing function
function compareInterval {
t1=$(normalize $1)
t2=$(normalize $2)
shift 2
while (( "$2" ))
do
a1=$(normalize $1)
a2=$(normalize $2)
if (( $t1 >= $a1 && $t2 <= $a2 ))
then
# got match
return 0
fi
shift 2
done
return 1
}
# sample values
t_initial=2.3
t_final=4.2
# Invocation. Compares against 1-3, 3-5, 2-5
if compareInterval $t_initial $t_final 1 3 3 5 2 5
then
echo Got match
fi
Wenn der Parameter der normalize
Funktion eine einfache Ganzzahl ist (also eine Zahl ohne Dezimalpunkt, z. B. 17
), können wir einfach mit 1000 multiplizieren, indem wir anhängen 000
, also 17
→ 17000
. Wenn der Parameter normalize
eine Gleitkommazahl ist (also einen Dezimalpunkt enthält, z. B. 42.5
), hängen wir trotzdem an 000
und verwenden dann sed
, um den Dezimalpunkt und alles nach der dritten Ziffer zu entfernen. Der sed
Befehl s/\(.*\)\.\(...\).*/\1\2/
nimmt einen String wieabcdef.Abonnieren
und RetourenAbonnieren, also 42.5
→ 42.5000
→ 42500
(dh 42,5 × 1000).
Es ist möglicherweise möglich, diese Zeichenfolgenmanipulation vollständig in durchzuführen bash
, ohne zu verwenden sed
.
Antwort4
Die Sache mit den „11..19“, nach der Sie fragen, wird als Klammererweiterung bezeichnet.
Sie können entweder verwenden eval {$t_initial..$t_final}
,
...oder
if `seq $t_initial..$t_final`==$somevalue