Bash: Verifique se ONE e SOMENTE uma das várias variáveis é igual a

3

Estou tentando formatar corretamente "e" quando se trata de tempo de leitura. Eu gostaria de tentar descobrir como ecoar apenas "e" antes do último valor (seja Horas, Minutos ou Segundos) e somente se houver mais de um valor (precisa de dois valores, ou seja: 2 Dias, e 5 minutos ou 2 dias, 12 horas e 5 minutos)

Eu não sei a sintaxe correta para dizer, se SOMENTE um dos valores, H, M e S for maior que 0, então $ DAnd="e". Isso também seria repetido por horas, portanto, se SOMENTE dos valores, M e S for maior que 0, "$ HAnd=" e ". Por minutos, é fácil, pois é apenas verificar se S é maior que 0.

Como se consegue isso sem criar uma grande confusão de códigos?

Meu script atual:

#!/bin/bash
TIME1="08/15/2018 10:30:41"
TIME2="08/30/2018 8:34:40"
SEC1='date +%s -d "${TIME1}"'
SEC2='date +%s -d "${TIME2}"'
DIFF='expr ${SEC2} - ${SEC1}'
CONVERTTIME()
{
  local T=$1
  local D=$((T/60/60/24))
  local H=$((T/60/60%24))
  local M=$((T/60%60))
  local S=$((T%60))
  if [[ $D > 0 ]]; then
    [[ ($H = 0 || $M = 0 || $S = 0) && ($H = 0 && $M = 0 && $S = 0) ]] && DComma="" || DComma=","
    #The problem
    [[ ($H > 0 || $M > 0 || $S > 0) && ($H = 0 || $M = 0 || $S = 0) ]] && DAnd="" || DAnd="and "
    [[ $D = 1 ]] && echo -n "$D day$DComma " || echo -n "$D days$DComma $DAnd"

  fi
  if [[ $H > 0 ]]; then
    [[ ($M = 0 || $S = 0) && ($M = 0 && $S = 0) ]] && HComma="" || HComma=","
    #The problem
    [[ ($M > 0 || $S > 0) && ($M = 0 || $S = 0) ]] && HAnd="" || HAnd="and "
    [[ $H = 1 ]] && echo -n "$H hour$HComma $HAnd" || echo -n "$H hours$HComma $HAnd"
  fi
  if [[ $M > 0 ]]; then
    [[ $S = 0 ]] && MComma="" || MComma=", and "
    [[ $M = 1 ]] && echo -n "$M minute$MComma" || echo -n "$M minutes$MComma"
  fi
  if [[ $S > 0 ]]; then
    [[ $S = 0 ]] && echo -n "$S second" || echo -n "$S seconds"
  fi
  # If no difference in time:
  [[ $D = 0 && $H = 0 && $M = 0 && $S = 0 ]] && echo -n "0 seconds"
  echo
}
echo
echo "TIME DIFFERENCE: $(CONVERTTIME $DIFF)"
echo
    
por eptesicus 22.08.2018 / 17:34

4 respostas

3

Eu escreveria isso como

seconds2text() {     
    local diff=$1
    local words=()
    if (( diff == 0 )); then
        words=("0 seconds")
    else
        local s=$((diff % 60))
        local m=$((diff / 60 % 60))
        local h=$((diff / 60 / 60 % 24))
        local d=$((diff / 60 / 60 / 24))

        (( d > 0 )) && { unit=day;    (( d > 1 )) && unit+=s; words+=("$d $unit"); };
        (( h > 0 )) && { unit=hour;   (( h > 1 )) && unit+=s; words+=("$h $unit"); }
        (( m > 0 )) && { unit=minute; (( m > 1 )) && unit+=s; words+=("$m $unit"); }
        (( s > 0 )) && { unit=second; (( s > 1 )) && unit+=s; words+=("$s $unit"); }
        (( ${#words[@]} > 1 )) && words[-1]="and ${words[-1]}"
    fi
    local IFS=,
    local text="${words[*]}"
    text=${text/,and/ and}
    echo "${text//,/, }"
}

e depois

$ for d in 0 1 61 3600 3601 3660 3661 86400 86401 86460 86461 90000 90001 90060 90061 180122; do seconds2text $d; done
0 seconds
1 second
1 minute and 1 second
1 hour
1 hour and 1 second
1 hour and 1 minute
1 hour, 1 minute and 1 second
1 day
1 day and 1 second
1 day and 1 minute
1 day, 1 minute and 1 second
1 day and 1 hour
1 day, 1 hour and 1 second
1 day, 1 hour and 1 minute
1 day, 1 hour, 1 minute and 1 second
2 days, 2 hours, 2 minutes and 2 seconds

Notas:

  • [[ $x > 0 ]] e [[ $x = 0 ]] estão fazendo uma comparação string , e não uma comparação numérica . Use ((x > 0)) e ((x == 0)) (e observe que o $ não é necessário).
  • sai do hábito de usar variáveis ALLCAPS. Deixe aqueles para o shell. Um dia você escreverá PATH=something e depois se perguntará por que seu roteiro está quebrado.
por 22.08.2018 / 18:12
1

com zsh :

#! /bin/zsh -
t1=${1?first date please}
t2=${2?second date please}

zmodload zsh/datetime
strftime -rst1 '%m/%d/%Y %H:%M:%S' "$t1" || exit
strftime -rst2 '%m/%d/%Y %H:%M:%S' "$t2" || exit

t=$((t2 - t1))
if ((t)) {
  and=' and' out= plural=s
  for unit duration (
    second 60
    minute 60
    hour   24
    day    7
    week   t+1
  ) {
    ((n = t % duration))
    ((t /= duration))
    ((n > 0)) && out="$and $n $unit${plural: n<2}$out" and=,
    ((t)) || break
  }
  out=${out#?* }
} else {
  out=now
}
echo "$out"

Então:

./that-script "08/15/2018 10:30:41" "08/30/2018 8:34:40"
2 weeks, 22 hours, 3 minutes and 59 seconds
    
por 22.08.2018 / 21:26
0

Que tal combinar o comando date com um script sed para uma diferença de data / hora menor que um ano)? Experimente

date +"%j d, %H h, %M m, %S s" -d@$(((d-90000))) | sed -r 's/, 0*00 .//g; s/365 d,* *//; s/ ([0-9]{2} [dhms])$/ and /; s/(^| )0+//g; s/^$/0 s/; s/([02-9] [dhms])/s/g; s/ d(s|,|$)/ day/; s/ h/ hour/; s/ m/ minute/; s/ s/ second/'

Roubando o loop de teste do post de glenn jackman:

for DIFF in 0 1 61 3600 3601 3660 3661 86400 86401 86460 86461 90000 90001 90060 90061 180122
  do date +"%j d, %H h, %M m, %S s" -d@$(((DIFF-90000))) |
sed -r '
s/, 0*00 .//g
s/365 d,* *//
s/ ([0-9]{2} [dhms])$/ and /
s/(^| )0+//g
s/^$/0 s/
s/([02-9] [dhms])/s/g
s/ d(s|,|$)/ day/
s/ h/ hour/
s/ m/ minute/
s/ s/ second/
'; done
0 seconds
1 second
1 minute, and 1 second
1 hour
1 hour, and 1 second
1 hour, and 1 minute
1 hour, 1 minute, and 1 second
1 day
1 day, and 1 second
1 day, and 1 minute
1 day, 1 minute, and 1 second
1 day, and 1 hour
1 day, 1 hour, and 1 second
1 day, 1 hour, and 1 minute
1 day, 1 hour, 1 minute, and 1 second
2 days, 2 hours, 2 minutes, and 2 seconds

(Eu sei que não irá adicionar o plural s em qualquer número que termine em 1, por exemplo, 11, 21 etc ...)

    
por 22.08.2018 / 22:29
0

Uma solução alternativa que imprime em até semanas:

s2t() {
    local i=${1:-0} j str w n c=0 res
    ((i==0))&&{ echo "0 seconds"; return; }         # with input zero result zero.

    #   s  m  h d w  <-- weigths
    w=(60 60 24 1 7)
    n=(seconds minutes hours days weeks)            # names.
    for j in 0 1 2; do  (( str[j]=i%w[j], i/=w[j] )); done  # calculate s m h.
    (( str[4]=i/w[4], i-=str[4]*w[4] ));            # calculate w.
    str[3]=$i                           # days remaining.

    for j in {4..0}; do
    ((str[j]==0)) && unset str[j]               # do not print empty items.
    res=$res${str[j]+"${str[j]} ${n[j]}, "}         # build the resulting string.
    ((str[j]==1))&&res=${res//"${n[j]}"/"${n[j]%?}"}    # remove trailing 's'.
    c=$(( c ${str[j]++1} ))                 # count the number of fields.
    done

    res=${res# }                        # remove leading empty space.
    res=${res%, }                       # remove trailing ', '
    ((c>1)) && res="${res%,*} and${res##*,}"            # add the 'and'

    echo "$res" # final string printed.
}

Teste (uma longa lista de valores):

$ a='0 1 60 3600 86400 604800 61 3601 86401 604801 3660 86460 604860 90000 608400 691200 3661 86461 604861 90001 608401 691201 90060 608460 691260 694800 90061 608461 691261 694801 694860 694861 2084583'

Resultados:

$ for d in $a; do s2t "$d"; done
0 seconds
1 second
1 minute
1 hour
1 day
1 week
1 minute and 1 second
1 hour and 1 second
1 day and 1 second
1 week and 1 second
1 hour and 1 minute
1 day and 1 minute
1 week and 1 minute
1 day and 1 hour
1 week and 1 hour
1 week and 1 day
1 hour, 1 minute and 1 second
1 day, 1 minute and 1 second
1 week, 1 minute and 1 second
1 day, 1 hour and 1 second
1 week, 1 hour and 1 second
1 week, 1 day and 1 second
1 day, 1 hour and 1 minute
1 week, 1 hour and 1 minute
1 week, 1 day and 1 minute
1 week, 1 day and 1 hour
1 day, 1 hour, 1 minute and 1 second
1 week, 1 hour, 1 minute and 1 second
1 week, 1 day, 1 minute and 1 second
1 week, 1 day, 1 hour and 1 second
1 week, 1 day, 1 hour and 1 minute
1 week, 1 day, 1 hour, 1 minute and 1 second
3 weeks, 3 days, 3 hours, 3 minutes and 3 seconds

Abaixo está uma segunda solução que se transforma em séculos que incluem meses de 30 dias e anos de 365 dias. Isso faz com que essa solução seja aproximada porque não há uma solução exata para meses (que devem estar próximos a 30,436875 em média) ou anos (que são em média de 364,2524).

Os valores do teste são muito mais longos:

a='0 1 60 3600 604800 2592000 31536000 86400 61 3601 604801 2592001 31536001 86401 3660 604860 2592060 31536060 86460 608400 2595600 31539600 90000 3196800 32140800 691200 34128000 2678400 31622400 3661 604861 2592061 31536061 86461 608401 2595601 31539601 90001 3196801 32140801 691201 34128001 2678401 31622401 608460 2595660 31539660 90060 3196860 32140860 691260 34128060 2678460 31622460 3200400 32144400 694800 34131600 2682000 31626000 34732800 3283200 32227200 34214400 608461 2595661 31539661 90061 3196861 32140861 691261 34128061 2678461 31622461 3200401 32144401 694801 34131601 2682001 31626001 34732801 3283201 32227201 34214401 3200460 32144460 694860 34131660 2682060 31626060 34732860 3283260 32227260 34214460 34736400 3286800 32230800 34218000 34819200 3200461 32144461 694861 34131661 2682061 31626061 34732861 3283261 32227261 34214461 34736401 3286801 32230801 34218001 34819201 34736460 3286860 32230860 34218060 34819260 34822800 34736461 3286861 32230861 34218061 34819261 34822801 34822860 34822861 9565268583'

O resultado também é bastante longo, mostrarei apenas alguns valores:

0 seconds
1 second
1 minute
1 hour
1 week
1 month
1 year
1 day
1 century and 1 second
1 day, 1 hour, 1 minute and 1 second
1 year, 1 week, 1 hour and 1 minute
3 centuries, 3 years, 3 months, 3 weeks, 3 days, 3 hours, 3 minutes and 3 seconds
    
por 25.08.2018 / 02:04

Tags