Bash: Check if ONE and ONLY ONE of several variables equals

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP











up vote
3
down vote

favorite












I'm trying to properly format "and" when it comes to reading time. I'd like to try to figure out how to only echo "and" before the last value (whether it be Hours, Minutes, or Seconds) and only if there's more than one value (need two values, ie: 2 Days, and 5 minutes, or 2 Days, 12 hours, and 5 minutes)



I don't know the proper syntax to say, if ONLY one of the values, H, M, and S is greater than 0, then $DAnd="and ".
This would be repeated for hours as well, so if ONLY of the values, M and S is greater than 0, then "$HAnd="and ".
For minutes, it's easy, as it's only checking to see if S is greater than 0.



How does one achieve this without making a large mess of code?



My current script:



#!/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()

echo
echo "TIME DIFFERENCE: $(CONVERTTIME $DIFF)"
echo






share|improve this question


















  • 1




    I recommend you install the dateutils package so you don't have to reinvent the wheel.
    – Wildcard
    Aug 22 at 16:14










  • @Wildcard, how would you do that with dateutils? Off the top of the man page, I can't see how to get that output format.
    – ilkkachu
    Aug 22 at 16:44










  • @ilkkachu, it was a general comment; I don't think dateutils can handle specifically the use case of including the "and" string before the final unit, and leaving out the units that are 0. But it can easily compute the diff between two dates (provided in any format you like) and print the output according to any static format string you like. So not an answer to this precise use case, but hopefully useful for future readers anyway.
    – Wildcard
    Aug 22 at 17:46














up vote
3
down vote

favorite












I'm trying to properly format "and" when it comes to reading time. I'd like to try to figure out how to only echo "and" before the last value (whether it be Hours, Minutes, or Seconds) and only if there's more than one value (need two values, ie: 2 Days, and 5 minutes, or 2 Days, 12 hours, and 5 minutes)



I don't know the proper syntax to say, if ONLY one of the values, H, M, and S is greater than 0, then $DAnd="and ".
This would be repeated for hours as well, so if ONLY of the values, M and S is greater than 0, then "$HAnd="and ".
For minutes, it's easy, as it's only checking to see if S is greater than 0.



How does one achieve this without making a large mess of code?



My current script:



#!/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()

echo
echo "TIME DIFFERENCE: $(CONVERTTIME $DIFF)"
echo






share|improve this question


















  • 1




    I recommend you install the dateutils package so you don't have to reinvent the wheel.
    – Wildcard
    Aug 22 at 16:14










  • @Wildcard, how would you do that with dateutils? Off the top of the man page, I can't see how to get that output format.
    – ilkkachu
    Aug 22 at 16:44










  • @ilkkachu, it was a general comment; I don't think dateutils can handle specifically the use case of including the "and" string before the final unit, and leaving out the units that are 0. But it can easily compute the diff between two dates (provided in any format you like) and print the output according to any static format string you like. So not an answer to this precise use case, but hopefully useful for future readers anyway.
    – Wildcard
    Aug 22 at 17:46












up vote
3
down vote

favorite









up vote
3
down vote

favorite











I'm trying to properly format "and" when it comes to reading time. I'd like to try to figure out how to only echo "and" before the last value (whether it be Hours, Minutes, or Seconds) and only if there's more than one value (need two values, ie: 2 Days, and 5 minutes, or 2 Days, 12 hours, and 5 minutes)



I don't know the proper syntax to say, if ONLY one of the values, H, M, and S is greater than 0, then $DAnd="and ".
This would be repeated for hours as well, so if ONLY of the values, M and S is greater than 0, then "$HAnd="and ".
For minutes, it's easy, as it's only checking to see if S is greater than 0.



How does one achieve this without making a large mess of code?



My current script:



#!/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()

echo
echo "TIME DIFFERENCE: $(CONVERTTIME $DIFF)"
echo






share|improve this question














I'm trying to properly format "and" when it comes to reading time. I'd like to try to figure out how to only echo "and" before the last value (whether it be Hours, Minutes, or Seconds) and only if there's more than one value (need two values, ie: 2 Days, and 5 minutes, or 2 Days, 12 hours, and 5 minutes)



I don't know the proper syntax to say, if ONLY one of the values, H, M, and S is greater than 0, then $DAnd="and ".
This would be repeated for hours as well, so if ONLY of the values, M and S is greater than 0, then "$HAnd="and ".
For minutes, it's easy, as it's only checking to see if S is greater than 0.



How does one achieve this without making a large mess of code?



My current script:



#!/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()

echo
echo "TIME DIFFERENCE: $(CONVERTTIME $DIFF)"
echo








share|improve this question













share|improve this question




share|improve this question








edited Aug 22 at 16:02

























asked Aug 22 at 15:34









eptesicus

233




233







  • 1




    I recommend you install the dateutils package so you don't have to reinvent the wheel.
    – Wildcard
    Aug 22 at 16:14










  • @Wildcard, how would you do that with dateutils? Off the top of the man page, I can't see how to get that output format.
    – ilkkachu
    Aug 22 at 16:44










  • @ilkkachu, it was a general comment; I don't think dateutils can handle specifically the use case of including the "and" string before the final unit, and leaving out the units that are 0. But it can easily compute the diff between two dates (provided in any format you like) and print the output according to any static format string you like. So not an answer to this precise use case, but hopefully useful for future readers anyway.
    – Wildcard
    Aug 22 at 17:46












  • 1




    I recommend you install the dateutils package so you don't have to reinvent the wheel.
    – Wildcard
    Aug 22 at 16:14










  • @Wildcard, how would you do that with dateutils? Off the top of the man page, I can't see how to get that output format.
    – ilkkachu
    Aug 22 at 16:44










  • @ilkkachu, it was a general comment; I don't think dateutils can handle specifically the use case of including the "and" string before the final unit, and leaving out the units that are 0. But it can easily compute the diff between two dates (provided in any format you like) and print the output according to any static format string you like. So not an answer to this precise use case, but hopefully useful for future readers anyway.
    – Wildcard
    Aug 22 at 17:46







1




1




I recommend you install the dateutils package so you don't have to reinvent the wheel.
– Wildcard
Aug 22 at 16:14




I recommend you install the dateutils package so you don't have to reinvent the wheel.
– Wildcard
Aug 22 at 16:14












@Wildcard, how would you do that with dateutils? Off the top of the man page, I can't see how to get that output format.
– ilkkachu
Aug 22 at 16:44




@Wildcard, how would you do that with dateutils? Off the top of the man page, I can't see how to get that output format.
– ilkkachu
Aug 22 at 16:44












@ilkkachu, it was a general comment; I don't think dateutils can handle specifically the use case of including the "and" string before the final unit, and leaving out the units that are 0. But it can easily compute the diff between two dates (provided in any format you like) and print the output according to any static format string you like. So not an answer to this precise use case, but hopefully useful for future readers anyway.
– Wildcard
Aug 22 at 17:46




@ilkkachu, it was a general comment; I don't think dateutils can handle specifically the use case of including the "and" string before the final unit, and leaving out the units that are 0. But it can easily compute the diff between two dates (provided in any format you like) and print the output according to any static format string you like. So not an answer to this precise use case, but hopefully useful for future readers anyway.
– Wildcard
Aug 22 at 17:46










4 Answers
4






active

oldest

votes

















up vote
3
down vote



accepted










I'd write that as



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//,/, "



and then



$ 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


Notes:




  • [[ $x > 0 ]] and [[ $x = 0 ]] are doing string comparison, not numeric comparison. Use ((x > 0)) and ((x == 0)) instead (and note that the $ is not required there).

  • get out of the habit of using ALLCAPS variables. Leave those for the shell. One day you'll write PATH=something and then wonder why your script is broken.





share|improve this answer




















  • in zsh, you could write path=something and still wonder why the script broke.
    – ilkkachu
    Aug 22 at 16:38

















up vote
1
down vote













With 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))
out=$out#?*
else
out=now

echo "$out"


Then:



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





share|improve this answer



























    up vote
    0
    down vote













    How about combining the date command with a sed script )for a date / time difference less than a year)? Try



    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 1/; s/(^| )0+/1/g; s/^$/0 s/; s/([02-9] [dhms])/1s/g; s/ d(s|,|$)/ day1/; s/ h/ hour/; s/ m/ minute/; s/ s/ second/'


    Stealing the test loop from glenn jackman's post:



    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 1/
    s/(^| )0+/1/g
    s/^$/0 s/
    s/([02-9] [dhms])/1s/g
    s/ d(s|,|$)/ day1/
    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


    (I know it will fail to add the plural s at any number ending in 1, e.g. 11, 21 etc...)






    share|improve this answer





























      up vote
      0
      down vote













      An alternative solution that prints up to weeks:



      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.



      Testing (a long list of values):



      $ 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'


      Results:



      $ 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


      Below is a second solution that transform up to centuries which include months of 30 days and years of 365 days. That makes this solution only approximate because there is no exact solution for months (that are supposed to be close to 30.436875 in average) or years (which are close to 364.2524 in average).



      The test values are a lot longer:



      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'


      The result is also quite long, I will show only some values:



      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





      share|improve this answer




















        Your Answer







        StackExchange.ready(function()
        var channelOptions =
        tags: "".split(" "),
        id: "106"
        ;
        initTagRenderer("".split(" "), "".split(" "), channelOptions);

        StackExchange.using("externalEditor", function()
        // Have to fire editor after snippets, if snippets enabled
        if (StackExchange.settings.snippets.snippetsEnabled)
        StackExchange.using("snippets", function()
        createEditor();
        );

        else
        createEditor();

        );

        function createEditor()
        StackExchange.prepareEditor(
        heartbeatType: 'answer',
        convertImagesToLinks: false,
        noModals: false,
        showLowRepImageUploadWarning: true,
        reputationToPostImages: null,
        bindNavPrevention: true,
        postfix: "",
        onDemand: true,
        discardSelector: ".discard-answer"
        ,immediatelyShowMarkdownHelp:true
        );



        );













         

        draft saved


        draft discarded


















        StackExchange.ready(
        function ()
        StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2funix.stackexchange.com%2fquestions%2f464169%2fbash-check-if-one-and-only-one-of-several-variables-equals%23new-answer', 'question_page');

        );

        Post as a guest






























        4 Answers
        4






        active

        oldest

        votes








        4 Answers
        4






        active

        oldest

        votes









        active

        oldest

        votes






        active

        oldest

        votes








        up vote
        3
        down vote



        accepted










        I'd write that as



        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//,/, "



        and then



        $ 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


        Notes:




        • [[ $x > 0 ]] and [[ $x = 0 ]] are doing string comparison, not numeric comparison. Use ((x > 0)) and ((x == 0)) instead (and note that the $ is not required there).

        • get out of the habit of using ALLCAPS variables. Leave those for the shell. One day you'll write PATH=something and then wonder why your script is broken.





        share|improve this answer




















        • in zsh, you could write path=something and still wonder why the script broke.
          – ilkkachu
          Aug 22 at 16:38














        up vote
        3
        down vote



        accepted










        I'd write that as



        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//,/, "



        and then



        $ 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


        Notes:




        • [[ $x > 0 ]] and [[ $x = 0 ]] are doing string comparison, not numeric comparison. Use ((x > 0)) and ((x == 0)) instead (and note that the $ is not required there).

        • get out of the habit of using ALLCAPS variables. Leave those for the shell. One day you'll write PATH=something and then wonder why your script is broken.





        share|improve this answer




















        • in zsh, you could write path=something and still wonder why the script broke.
          – ilkkachu
          Aug 22 at 16:38












        up vote
        3
        down vote



        accepted







        up vote
        3
        down vote



        accepted






        I'd write that as



        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//,/, "



        and then



        $ 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


        Notes:




        • [[ $x > 0 ]] and [[ $x = 0 ]] are doing string comparison, not numeric comparison. Use ((x > 0)) and ((x == 0)) instead (and note that the $ is not required there).

        • get out of the habit of using ALLCAPS variables. Leave those for the shell. One day you'll write PATH=something and then wonder why your script is broken.





        share|improve this answer












        I'd write that as



        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//,/, "



        and then



        $ 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


        Notes:




        • [[ $x > 0 ]] and [[ $x = 0 ]] are doing string comparison, not numeric comparison. Use ((x > 0)) and ((x == 0)) instead (and note that the $ is not required there).

        • get out of the habit of using ALLCAPS variables. Leave those for the shell. One day you'll write PATH=something and then wonder why your script is broken.






        share|improve this answer












        share|improve this answer



        share|improve this answer










        answered Aug 22 at 16:12









        glenn jackman

        47k265103




        47k265103











        • in zsh, you could write path=something and still wonder why the script broke.
          – ilkkachu
          Aug 22 at 16:38
















        • in zsh, you could write path=something and still wonder why the script broke.
          – ilkkachu
          Aug 22 at 16:38















        in zsh, you could write path=something and still wonder why the script broke.
        – ilkkachu
        Aug 22 at 16:38




        in zsh, you could write path=something and still wonder why the script broke.
        – ilkkachu
        Aug 22 at 16:38












        up vote
        1
        down vote













        With 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))
        out=$out#?*
        else
        out=now

        echo "$out"


        Then:



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





        share|improve this answer
























          up vote
          1
          down vote













          With 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))
          out=$out#?*
          else
          out=now

          echo "$out"


          Then:



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





          share|improve this answer






















            up vote
            1
            down vote










            up vote
            1
            down vote









            With 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))
            out=$out#?*
            else
            out=now

            echo "$out"


            Then:



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





            share|improve this answer












            With 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))
            out=$out#?*
            else
            out=now

            echo "$out"


            Then:



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






            share|improve this answer












            share|improve this answer



            share|improve this answer










            answered Aug 22 at 19:26









            Stéphane Chazelas

            283k53521854




            283k53521854




















                up vote
                0
                down vote













                How about combining the date command with a sed script )for a date / time difference less than a year)? Try



                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 1/; s/(^| )0+/1/g; s/^$/0 s/; s/([02-9] [dhms])/1s/g; s/ d(s|,|$)/ day1/; s/ h/ hour/; s/ m/ minute/; s/ s/ second/'


                Stealing the test loop from glenn jackman's post:



                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 1/
                s/(^| )0+/1/g
                s/^$/0 s/
                s/([02-9] [dhms])/1s/g
                s/ d(s|,|$)/ day1/
                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


                (I know it will fail to add the plural s at any number ending in 1, e.g. 11, 21 etc...)






                share|improve this answer


























                  up vote
                  0
                  down vote













                  How about combining the date command with a sed script )for a date / time difference less than a year)? Try



                  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 1/; s/(^| )0+/1/g; s/^$/0 s/; s/([02-9] [dhms])/1s/g; s/ d(s|,|$)/ day1/; s/ h/ hour/; s/ m/ minute/; s/ s/ second/'


                  Stealing the test loop from glenn jackman's post:



                  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 1/
                  s/(^| )0+/1/g
                  s/^$/0 s/
                  s/([02-9] [dhms])/1s/g
                  s/ d(s|,|$)/ day1/
                  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


                  (I know it will fail to add the plural s at any number ending in 1, e.g. 11, 21 etc...)






                  share|improve this answer
























                    up vote
                    0
                    down vote










                    up vote
                    0
                    down vote









                    How about combining the date command with a sed script )for a date / time difference less than a year)? Try



                    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 1/; s/(^| )0+/1/g; s/^$/0 s/; s/([02-9] [dhms])/1s/g; s/ d(s|,|$)/ day1/; s/ h/ hour/; s/ m/ minute/; s/ s/ second/'


                    Stealing the test loop from glenn jackman's post:



                    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 1/
                    s/(^| )0+/1/g
                    s/^$/0 s/
                    s/([02-9] [dhms])/1s/g
                    s/ d(s|,|$)/ day1/
                    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


                    (I know it will fail to add the plural s at any number ending in 1, e.g. 11, 21 etc...)






                    share|improve this answer














                    How about combining the date command with a sed script )for a date / time difference less than a year)? Try



                    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 1/; s/(^| )0+/1/g; s/^$/0 s/; s/([02-9] [dhms])/1s/g; s/ d(s|,|$)/ day1/; s/ h/ hour/; s/ m/ minute/; s/ s/ second/'


                    Stealing the test loop from glenn jackman's post:



                    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 1/
                    s/(^| )0+/1/g
                    s/^$/0 s/
                    s/([02-9] [dhms])/1s/g
                    s/ d(s|,|$)/ day1/
                    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


                    (I know it will fail to add the plural s at any number ending in 1, e.g. 11, 21 etc...)







                    share|improve this answer














                    share|improve this answer



                    share|improve this answer








                    edited Aug 22 at 20:36

























                    answered Aug 22 at 20:29









                    RudiC

                    1,1616




                    1,1616




















                        up vote
                        0
                        down vote













                        An alternative solution that prints up to weeks:



                        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.



                        Testing (a long list of values):



                        $ 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'


                        Results:



                        $ 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


                        Below is a second solution that transform up to centuries which include months of 30 days and years of 365 days. That makes this solution only approximate because there is no exact solution for months (that are supposed to be close to 30.436875 in average) or years (which are close to 364.2524 in average).



                        The test values are a lot longer:



                        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'


                        The result is also quite long, I will show only some values:



                        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





                        share|improve this answer
























                          up vote
                          0
                          down vote













                          An alternative solution that prints up to weeks:



                          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.



                          Testing (a long list of values):



                          $ 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'


                          Results:



                          $ 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


                          Below is a second solution that transform up to centuries which include months of 30 days and years of 365 days. That makes this solution only approximate because there is no exact solution for months (that are supposed to be close to 30.436875 in average) or years (which are close to 364.2524 in average).



                          The test values are a lot longer:



                          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'


                          The result is also quite long, I will show only some values:



                          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





                          share|improve this answer






















                            up vote
                            0
                            down vote










                            up vote
                            0
                            down vote









                            An alternative solution that prints up to weeks:



                            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.



                            Testing (a long list of values):



                            $ 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'


                            Results:



                            $ 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


                            Below is a second solution that transform up to centuries which include months of 30 days and years of 365 days. That makes this solution only approximate because there is no exact solution for months (that are supposed to be close to 30.436875 in average) or years (which are close to 364.2524 in average).



                            The test values are a lot longer:



                            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'


                            The result is also quite long, I will show only some values:



                            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





                            share|improve this answer












                            An alternative solution that prints up to weeks:



                            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.



                            Testing (a long list of values):



                            $ 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'


                            Results:



                            $ 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


                            Below is a second solution that transform up to centuries which include months of 30 days and years of 365 days. That makes this solution only approximate because there is no exact solution for months (that are supposed to be close to 30.436875 in average) or years (which are close to 364.2524 in average).



                            The test values are a lot longer:



                            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'


                            The result is also quite long, I will show only some values:



                            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






                            share|improve this answer












                            share|improve this answer



                            share|improve this answer










                            answered Aug 25 at 0:04









                            Isaac

                            6,8001834




                            6,8001834



























                                 

                                draft saved


                                draft discarded















































                                 


                                draft saved


                                draft discarded














                                StackExchange.ready(
                                function ()
                                StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2funix.stackexchange.com%2fquestions%2f464169%2fbash-check-if-one-and-only-one-of-several-variables-equals%23new-answer', 'question_page');

                                );

                                Post as a guest













































































                                Comments

                                Popular posts from this blog

                                Long meetings (6-7 hours a day): Being “babysat” by supervisor

                                Is the Concept of Multiple Fantasy Races Scientifically Flawed? [closed]

                                Confectionery