Foreach hour:min:sec.ms - bash

I would like to know how can I parse an foreach specifying hours, minutes, seconds and milliseconds. Something like this:
DURATION=00:14:17.82 // (00 hours, 14 minutes, 17 seconds and 82ms)
for ((i=1;i<=DURATION;i++)); do
// output the hour, minute, second and ms here
done
I want to iterate every milisecond, second, minute, hour! Like this:
00:14:17.82
00:14:17.83
00:14:17.84
00:14:17.85
...
00:14:18.01
00:14:18.02
...
01:00:10.15
01:00:10.16
01:00:10.17
...
I just know creating this for seconds and just seconds! But how can I make for hours, minutes, seconds and ms? Thank you so much!

NOTE: The following ideas assume a max DURATION of 23:59:59:99 or 24:00:00.00; upper ranges can certainly be increased but keep in mind the extra overhead (cpu and/or memory) for brace expansions, especially if generating huge ranges that dwarf DURATION (ie, generating large volumes of timestamps that you'll never process).
If you're going to manually enter the value for DURATION then you could manually enter the following brace expansion series:
for i in {00..00}:{00..14}:{00..17}.{00..82}
do
echo $i
done
NOTE: This relatively small set of of brace expansions was almost instantaneous and took up ~6 MB (RAM) on my system.
This generates:
00:00:00.00
00:00:00.01
00:00:00.02
00:00:00.03
00:00:00.04
... snip ...
00:14:17.78
00:14:17.79
00:14:17.80
00:14:17.81
00:14:17.82
Alternatively:
DURATION=00:14:17.82
for i in {00..23}:{00..59}:{00..59}.{00..99}
do
[[ "${i}" > "${DURATION}" ]] && break
# do other stuff
done
NOTE:
on my system this took about about 10 seconds and 1.6 GB (RAM) to expand the series of brace expansions before the loop was processed
this particular set of brace expansions creates 8.64 million timestamps but ...
we break out of the loop after processing ~ 86K timestamps (or ~1% of the timestamps generated by the brace expansions) so ...
we wasted a lot of cpu cycles and RAM generating timestamps that we never got around to using
Another idea is to write a 4-deep set of for loops and break out once we hit 00:14:17.82, eg:
DURATION='00:14:17.82'
for a in {00..23}; do
for b in {00..59}; do
for c in {00..59}; do
for d in {00..99}; do
[[ "${a}:${b}:${c}.${d}" > "${DURATION}" ]] && break 4
# do other stuff here
done
done
done
done
NOTE: While each brace expansion is fast (and uses a miniscule amount of RAM), the nested/repeated expansions is going to eat up a lot of time the longer it takes to get to ${DURATION}; however, unlike the previous ideas this one will at least let you start processing your timestamp values immediately, with intermittent (brief) delays as a new brace expansion is performed.
Of course we could just skip the brace expansion overhead and use 'normal' for loops, eg:
DURATION='00:14:17.82'
for ((a=0; a<=23; a++)); do
for ((b=0; b<=59; b++)); do
for ((c=0; c<=59; c++)); do
for ((d=0; d<=99; d++)); do
printf -v ts "%02d:%02d:%02d.%02d" ${a} ${b} ${c} ${d}
[[ "${ts}" > "${DURATION}" ]] && break 4
# do other stuff here
done
done
done
done
NOTES:
this eliminates the cpu/time/RAM overhead of the brace expansions
assumes you have a newer version of bash that supports printf -v
And for the masochists:
DURATION='00:14:17.82'
for ((i=0; i<8640000; i++)); do
hr=$(( ( i / 100 / 60 / 60 ) % 24 ))
min=$(( ( i / 100 / 60 ) % 60 ))
sec=$(( ( i / 100 ) % 60 ))
msec=$(( i % 100 ))
printf -v ts "%02d:%02d:%02d.%02d" ${hr} ${min} ${sec} ${msec}
[[ "${ts}" > "${DURATION}" ]] && break
# do other stuff
done

Just iterate over milliseconds and convert to the representation you want upon use. First, we write conversion functions:
millis_to_human() {
local h m s ms
# calculate all those hours, minutes, seconds...
((
ms = $1,
s = ms / 100,
ms %= 100,
m = s / 60,
s %= 60,
h = m / 60,
h %= 60,
1
))
printf "%02d:%02d:%02d.%02d\n" "$h" "$m" "$s" "$ms"
}
human_to_millis() {
# Match with regex
[[ $1 =~ ([0-9]*):([0-9]*):([0-9]*).([0-9]*) ]]
# Using 10# so that the number is always in base 10, even with leading 0.
echo $(( ( (
10#${BASH_REMATCH[1]} * 60 +
10#${BASH_REMATCH[2]} ) * 60 +
10#${BASH_REMATCH[3]} ) * 100 +
10#${BASH_REMATCH[4]} ))
}
Then just:
duration='00:14:17.82'
duration_ms=$(human_to_millis "$duration")
for ((i = 1; i <= duration_ms; ++i)); do
millis_to_human "$i"
done

Related

Creating function to perform display-time arithmetic

Trying to create a function to get a number for hours(0-24) and a number for minute(0-60) and add 15 minutes to it. Issue is when I try to put a number 45 and up, I get a number over 60.
Created if then statement that takes my input and if the minutes are over 60, it will subtract 60 from minutes variable and add 1 to hour variable.
Expected results are that it will convert the time if over 60 minutes into 1 hour and remainder into minutes. The actual results are that sometimes the if then statement doesn't run correctly and it does both if AND then statements or sometimes I get a negative number.
#!/bin/bash
read -p "Hours: " hr1
read -p "Minutes: " min1
min1=$((min1+15))
Time() {
echo $min1
if $min1 > 60
then
min1=$((min1-60))
if $min1 < 0
then $((min1*-1))
fi
hr1=$((hr1+1))
else
$min1 + 15
fi
}
Time
echo $min1
echo $hr1
You can greatly simplify your Time() function by using shell arithmetic expressions delimited by (( and )). Here stand-alone arithmetic expressions are only valid with Bash shell but not posix shells where it is undefined
(( hr1 += min1 / 60, min1 %= 60 ))
Splits into two expressions separated by a comma ,:
hr1 += min1 / 60 ⇔ hr1 = hr1 + min1 / 60
min1 / 60 is evaluated in priority to the addition.
Shell arithmetic is integer only
min1 %= 60 ⇔ min1 = min1 % 60
The modulo reminder of min1 / 60
#!/usr/bin/env bash
read -r -p 'Hours: ' hr1
read -r -p 'Minutes: ' min1
# Bash stand-alone arithmetic expression add 15 to min1
(( min1+=15 ))
Time() {
echo "${min1}"
# Two Bash stand-alone arithmetic expressions separated by ,
# Add integer division of min1 by 60 to hr1
# Truncate min1 to the integer division reminder of itself by 60
(( hr1 += min1 / 60, min1 %= 60 ))
}
Time
echo "${min1}"
echo "${hr1}"
There are many, many ways to add minutes to a given time and then obtain the resulting hours and minutes. A systematic way is using the date function as Oliver Gaida shows. A manual conversion is fine for learning purposes, but you will want to ensure you deal with the addition of time that causes the hours to roll into the next day.
In order to handle all aspects of the conversion, it is useful to convert the total time to time into seconds. At that point, you can perform all necessary tests to determine if the total time has rolled the hours into the next day, etc..
Since you seem to only want to capture the hours and minutes of the resulting time and are not concerned with the number of days, you can simply test the number of seconds against the seconds-per-day and if the number of seconds exceeds seconds-per-day, simply reduce the number of seconds modulo by seconds-per-day.
A short function getHM() (your Time() function) to update the values in hr1 and min1 could be similar to the following:
## function converting number of seconds to hours, minutes (discarding days)
getHM() {
test -z "$1" && { ## validate input given
printf "error: insufficient arguments getHM()\n" >&2
return 1
}
local secs="$1" ## local variables seconds
local secsperday=$((3600 * 24)) ## seconds-per-day
local days=$((secs / secsperday)) ## days
(( days > 0 )) && { ## seconds exceed seconds-per-day
printf "error: time exceeds 24 hours, days discarded.\n" >&2
secs=$((secs % secsperday)) ## reduce secs modulo by secsperday
}
hr1=$((secs / 3600)) ## update hr1 & min1 values
min1=$(((secs - hr1 * 3600) / 60))
}
As noted in the function, it simply discards any additional days in order to return the resulting hours (0-23) and minutes (0-59).
Adding that to a short example and you could do:
#!/bin/bash
declare -i hr1=-1 min1=-1 addminutes=15 ## initialize variables
## function converting number of seconds to hours, minutes (discarding days)
getHM() {
test -z "$1" && { ## validate input given
printf "error: insufficient arguments getHM()\n" >&2
return 1
}
local secs="$1" ## local variables seconds
local secsperday=$((3600 * 24)) ## seconds-per-day
local days=$((secs / secsperday)) ## days
(( days > 0 )) && { ## seconds exceed seconds-per-day
printf "error: time exceeds 24 hours, days discarded.\n" >&2
secs=$((secs % secsperday)) ## reduce secs modulo by secsperday
}
hr1=$((secs / 3600)) ## update hr1 & min1 values
min1=$(((secs - hr1 * 3600) / 60))
}
## loop until valid input received
while ((hr1 < 0)) || ((hr1 > 23)) || ((min1 < 0)) || ((min1 > 59)); do
read -p "Hours: " hr1
read -p "Minutes: " min1
done
## convert all to seconds adding desired 15 minutes
secs=$((hr1 * 3600 + (min1 + addminutes) * 60))
getHM "$secs" ## call function to update hr1 & min1
printf "\nHours : %d\nMinutes : %d\n" "$hr1" "$min1"
(note: when using the arithmetic operator for comparison, e.g. ((...)), any non-integer values are evaluated as 0, so if you want to validate the use provides only integer input, you need to do that in the while loop after the read is complete -- and reset either variable to -1 if a non-integer value is detected)
Example Use/Output
No adjustment in hour required:
$ bash gethm.sh
Hours: 23
Minutes: 44
Hours : 23
Minutes : 59
Addition causing total time to land precisely at the start of a day:
$ bash gethm.sh
Hours: 23
Minutes: 45
error: time exceeds 24 hours, days discarded.
Hours : 0
Minutes : 0
(note: the error message provided if the total time causes the hours to roll into the next day. It is informational only and you can remove it to suit your needs)
Example showing the roll to 1 minute past the new day:
$ bash gethm.sh
Hours: 23
Minutes: 46
error: time exceeds 24 hours, days discarded.
Hours : 0
Minutes : 1
Look things over and let me know if you have further questions
With date it is easy. Convert the date to epoche-seconds, add 900 seconds and convert it back.
date --date="#$(echo $(($(date --date="22:53" +"%s")+900)))" +"%H:%M"
23:08

How can I create a stopwatch in bash?

I created a simple stopwatch (bash function) for counting time, but for now it's showing current time with milliseconds.
The code:
function stopwatch() {
date +%H:%M:%S:%N
while true; do echo -ne "`date +%H:%M:%S:%N`\r"; done;
}
I tried to change it as explained in this answer, but it works only with second since Unix Epoch.
When I used date format +%s.%N the subtraction from the answer above stopped working due to the fact that bash subtraction takes only integer.
How can I solve it and have a terminal stopwatch that prints time like so:
0.000000000
0.123123123
0.435345345
(and so on..)
?
One possible (& hacky) mechanism that can work for a day:
$ now=$(date +%s)sec
$ while true; do
printf "%s\r" $(TZ=UTC date --date now-$now +%H:%M:%S.%N)
sleep 0.1
done
Bonus: You can press enter at any time to get the LAP times. ;-)
Note: This is a quick fix. Better solutions should be available...
watch based variant (same logic):
$ now=$(date +%s)sec; watch -n0.1 -p TZ=UTC date --date now-$now +%H:%M:%S.%N
If you want something simple that includes minutes, seconds, and centiseconds like a traditional stopwatch you could use sw.
Install
wget -q -O - http://git.io/sinister | sh -s -- -u https://raw.githubusercontent.com/coryfklein/sw/master/sw
Usage
# start a stopwatch from 0, save start time in ~/.sw
sw
# resume the last run stopwatch
sw --resume
time cat
then press Ctrl-c or Ctrl-d to stop the timer and show the time. The first number is the time.
I've further refined it into this bash alias
alias stopwatch="echo Press Ctrl-c to stop the timer; TIMEFORMAT=%R; time cat; unset TIMEFORMAT"
Here's a nicer function I grabbed a while ago:
function stopwatch() {
local BEGIN=$(date +%s)
echo Starting Stopwatch...
while true; do
local NOW=$(date +%s)
local DIFF=$(($NOW - $BEGIN))
local MINS=$(($DIFF / 60))
local SECS=$(($DIFF % 60))
local HOURS=$(($DIFF / 3600))
local DAYS=$(($DIFF / 86400))
printf "\r%3d Days, %02d:%02d:%02d" $DAYS $HOURS $MINS $SECS
sleep 0.5
done
}
Based on a gist by rawaludin:
function stopwatch() {
local BEGIN=$(date +%s)
while true; do
local NOW=$(date +%s)
local DIFF=$(($NOW - $BEGIN))
local MINS=$(($DIFF / 60 % 60))
local SECS=$(($DIFF % 60))
local HOURS=$(($DIFF / 3600 % 24))
local DAYS=$(($DIFF / 86400))
local DAYS_UNIT
[ "$DAYS" == 1 ] && DAYS_UNIT="Day" || DAYS_UNIT="Days"
printf "\r %d %s, %02d:%02d:%02d " $DAYS $DAYS_UNIT $HOURS $MINS $SECS
sleep 0.25
done
}
For people who are not familiar with this: in English, only when it is 1 do we use singular -- Day. When it is 0, 2, 3, 4, 5..., we use plural
"Days", so note that it is 0 Days.
Here is another take on a bash stopwatch, drawing much from other answers in this thread. Ways in which this version differs from the others include:
This version uses bash arithmetic rather than calling bc which I found (by timing it) to be way less cpu time.
I have addressed the 25th-hour limitation that someone had pointed out by tacking 24 hours onto the hour part for every day elapsed. (So now I guess it's the ~31st-day limitation.)
I leave the cursor just to the right of the output, unlike the version in the accepted answer. That way you can easily measure laps (or more generally mark important event times) just by hitting enter, which will move the timer to the next line, leaving the time at keypress visible.
#!/bin/bash
start_time=$(date +%s)
while true; do
current_time=$(date +%s)
seconds_elapsed=$(( $current_time - $start_time ))
timestamp=$(date -d"#$seconds_elapsed" -u +%-d:%-H:%-M:%-S)
IFS=':' read -r day hour minute second <<< "$timestamp"
hour="$(( $hour+24*($day-1) ))"
printf "\r%02d:%02d:%02d" $hour $minute $second
sleep 0.5
done;
Here is sample output from running stopwatch (as an executable script in the PATH) and hitting the return key at 7 and 18 seconds, and hitting Ctrl-C after about 9 minutes:
$ stopwatch
00:00:07
00:00:18
00:09:03^C
$
Notes:
I use the +%-d:%-H:%-M:%-S output format for date (this dashes mean "leave off any leading zero please") because printf seems to interpret digit strings with a leading zero as octal and eventually complains about invalid values.
I got rid of the nanoseconds simply because for my purposes I don't need beyond 1-second precision. Therefore I adjusted the sleep duration to be longer to save on compute.
For the subtraction you should use bc (An arbitrary precision calculator language).
Here is the example code that fulfill your requirements:
function stopwatch() {
date1=`date +%s.%N`
while true; do
curr_date=`date +%s.%N`
subtr=`echo "$curr_date - $date1" | bc`
echo -ne "$subtr\r";
sleep 0.03
done;
}
Additional sleep is added to lower the CPU usage (without it on my machine it was almost 15% and with this sleep it lowered to 1%).

How to round a large number in Shell command?

In Mac terminal, I would like to round a large number.
For example,
At 10^13th place:
1234567812345678 --> 1230000000000000
Or at 10^12th place:
1234567812345678 --> 1235000000000000
So I would like to specify the place, and then get the rounded number.
How do I do this?
You can use arithmetic expansion:
$ val=1234567812345678
$ echo $(( ${val: -13:1} < 5 ? val - val % 10**13 : val - val % 10**13 + 10**13 ))
1230000000000000
$ echo $(( ${val: -12:1} < 5 ? val - val % 10**12 : val - val % 10**12 + 10**12 ))
1235000000000000
This checks if the most significant removed digit is 5 or greater, and if it is, the last significant unremoved digit is increased by one; then we subtract the division remainder from the (potentially modified) initial value.
If you don't want to have to write it this way, you can wrap it in a little function:
round () {
echo $(( ${1: -$2:1} < 5 ? $1 - $1 % 10**$2 : $1 - $1 % 10**$2 + 10**$2 ))
}
which can then be used like this:
$ round "$val" 13
1230000000000000
$ round "$val" 12
1235000000000000
Notice that quoting $val isn't strictly necessary here, it's just a good habit.
If the one-liner is too cryptic, this is a more readable version of the same:
round () {
local rounded=$(( $1 - $1 % 10**$2 )) # Truncate
# Check if most significant removed digit is >= 5
if (( ${1: -$2:1} >= 5 )); then
(( rounded += 10**$2 ))
fi
echo $rounded
}
Apart from arithmetic expansion, this also uses parameter expansion to get a substring: ${1: -$2:1} stands for "take $1, count $2 from the back, take one character". There has to be a space before -$2 (or is has to be in parentheses) because otherwise it would be interpreted as a different expansion, checking if $1 is unset or null, which we don't want.
awk's [s]printf function can do rounding for you, within the limits of double-precision floating-point arithmetic:
$ for p in 13 12; do
awk -v p="$p" '{ n = sprintf("%.0f", $0 / 10^p); print n * 10^p }' <<<1234567812345678
done
1230000000000000
1235000000000000
For a pure bash implementation, see Benjamin W.'s helpful answer.
Actually, if you want to round to n significant digits you might be best served by mixing up traditional math and strings.
Serious debugging is left to the student, but this is what I quickly came up with for bash shell and hope MAC is close enough:
function rounder
{
local value=$1;
local digits=${2:-3};
local zeros="$( eval "printf '0%.0s' {1..$digits}" )"; #proper zeros
# a bit of shell magic that repats the '0' $digits times.
if (( value > 1$zeros )); then
# large enough to require rounding
local length=${#value};
local digits_1=$(( $digits + 1 )); #digits + 1
local tval="${value:0:$digits_1}"; #leading digits, plus one
tval=$(( $tval + 5 )); #half-add
local tlength=${#tval}; #check if carried a digit
local zerox="";
if (( tlength > length )); then
zerox="0";
fi
value="${tval:0:$digits}${zeros:0:$((length-$digits))}$zerox";
fi
echo "$value";
}
See how this can be done much shorter, but that's another exercise for the student.
Avoiding floating point math due to the inherit problems within.
All sorts of special cases, like negative numbers, are not covered.

Print elapsed time in secs with three trailing digits

For a framework I'm writing I would like to measure how much a piece of (bash) code takes to execute and then print the elapsed time in seconds.
For measuring, I do the following:
start=$(date +%s%N)
# do something here
elapsed_time=$(($(date +%s%N) - start))
This gives me the elapsed seconds concatenated with the elapsed nanos. If I now divide this by 1000000, I'll get the time in ms
elapsed_time_in_ms=$(($elapsed time / 1000000))
This is all nice and seems to work but the real problem is that I want to print it in this format:
12.789s
where before the . are the seconds and after the dot are last 3 digits of the ms value.
How would I achieve something like that?
EDIT
I am aware that the time would probably not of much use, still I would like to implement this (even if only for cosmetic reasons :-)).
EDIT 2
For anyone facing the same problem:
In the end I've chosen to use time as it doesn't require a fork and seems to be the most portable solution.
Have a look at the it function and the global total_elapsed_time variable here to see how I implemented this.
You can use bc command:
elapsed_time_in_ms=$(echo "scale=3;$elapsed_time/1000000" | bc)
The scale basically sets the number of digits you want after the .
This can also be done using shell parameter expansion or via printf. For example, running the following two sequences one after the other printed out 1349883230.715 and 1349883230.721 in one run, and 1349884003.025 and 1349884003.032 in another. (The %N date format fills with leading zeroes.)
s=$(date +%s.%N); s=${s%??????}; echo $s
t=$(printf "%20.3f" $(date +%s.%N)); echo $t
As mentioned in man bash under Pattern Matching, special pattern character ? matches any single character. As mentioned under Parameter Expansion, the form
${parameter%word}
removes a matching suffix pattern: “If the pattern matches a trailing portion ... the expansion is the expanded value of parameter with the shortest matching pattern (the "%" case) or the longest matching pattern (the "%%" case) deleted.”
Just cut the digits after the first three off from the nanoseconds.
printf "%d.%.3ss\n" date +%S date +%N # It might not be terribly unwise to run date only once, btw.
-- print a digit followed by a dot, then treat the next argument as a string and print only the first three characters.
$ i=0; while [ $i -lt 9 ];do i=$((i+1)); sleep 0.1; \
printf "%d.%.3ss\n" `date +%S` `date +%N` ;done
33.917s
34.025s
34.133s
34.240s
34.348s
34.457s
34.566s
34.674s
34.784s
As using bc implie forks which take time, I prefer this pure bash solution:
start=$(date +%s%N);sleep 1.1;elapsed_time=$(($(date +%s%N) - start))
_Z9=000000000
[ ${#elapsed_time} -lt 9 ] && \
elapsed_time=${_Z9:0:9-${#elapsed_time}}$elapsed_time;
printf "%.3fs\n" ${elapsed_time:0:${#elapsed_time}-9
}.${elapsed_time:${#elapsed_time}-9}
print:
1.107s
For that kind of things, I've wrote a little bash source file that will do
the job quickly.
It use on Linux /proc/timer_list, or on Linux /proc/uptime or date +%s%n not well tested on other systems (feed-back would be welcome ;).
It use two counters (one for each invocation and the other as an overall counter) and accept some arguments (read comments):
. elap.bash
elap -R ; for i in {1..10};do sleep .1; elap Counter $i;done;elap -t total
0.110488478 Counter 1
0.111014783 Counter 2
0.117158015 Counter 3
0.112897232 Counter 4
0.111928207 Counter 5
0.108822248 Counter 6
0.113464053 Counter 7
0.117487421 Counter 8
0.115716626 Counter 9
0.110493433 Counter 10
0.008513430 1.137983926 total
And, as bc could be useful:
time echo 'scale=1000;pi=4*a(1);0'|bc -l
0
real 0m1.590s
user 0m0.768s
sys 0m0.008s
or
elap -R;elap BC answer is: $(echo 'scale=1000;pi=4*a(1);0'|bc -l )
1.539957483 BC answer is: 0
Using this script with /proc/timer_list could do:
elap -R ; for i in {1..10};do elap Counter $i;done;elap -t total
0.001299574 Counter 1
0.001574097 Counter 2
0.005771637 Counter 3
0.001428803 Counter 4
0.010423721 Counter 5
0.004037965 Counter 6
0.001392464 Counter 7
0.008092812 Counter 8
0.001634280 Counter 9
0.001365652 Counter 10
0.008201473 0.045222478 total
While same script whithout access to /proc, using fork to date +%s%N give:
elap -R ; for i in {1..10};do elap Counter $i;done;elap -t total
0.012148259 Counter 1
0.013415551 Counter 2
0.008279329 Counter 3
0.013700332 Counter 4
0.012837796 Counter 5
0.015562929 Counter 6
0.008062369 Counter 7
0.016810494 Counter 8
0.011537439 Counter 9
0.009731194 Counter 10
0.012959840 0.135045532 total
Where we could see that fork have a cost (near 1/100th sec in this case).
Well, finally, for matching exact format for SO question this little script could be patched, for sample in this way:
eval "$(sed -e < elap.bash '
/6d/{ s/6d.%/10.3f/g;p;N;
:a;
s/^.*\n//g;N;s/" \\\n[ \t]*"/./; p;
s/^.*//g; N;/elaP_elap2/ba; }')"
elap -R ; for i in {1..10};do elap Counter $i;done;elap -t total
0.001s Counter 1
0.006s Counter 2
0.007s Counter 3
0.004s Counter 4
0.003s Counter 5
0.002s Counter 6
0.001s Counter 7
0.001s Counter 8
0.006s Counter 9
0.002s Counter 10
0.004s 0.038s total
38s total

How can I shift digits in bash?

I have a homework assignment that is asking to shift a decimal number by a specified amount of digits. More clearly this bash script will take two input arguments, the first is the number(maximum 9 digits) that the shift will be performed on and the second is the number(-9 to 9) of digits to shift. Another requirement is that when a digit is shifted off the end, it should be attached to the other end of the number. One headache of a requirement is that we cannot use control statements of any kind: no loops, no if, and switch cases.
Example: 12345 3 should come out to 345000012 and 12345 -3 should be 12345000
I know that if I mod 12345 by 10^3 I get 345 and then if I divide 12345 by 10^3 I get 12 and then I can just concatenate those two variables together to get 34512. I am not quite sure if that is exactly correct but that is the closest I can get as of now. As far as the -3 shift, I know that 10^-3 is .001 and would work however when I try using 10^-3 in bash I get an error.
I am just lost at this point, any tips would be greatly appreciated.
EDIT: After several hours of bashing (pun intended) my head against this problem, I finally came up with a script that for the most part works. I would post the code right now but I fear another student hopelessly lost might stumble upon it. I will check back and post what I came up with in a week or two. I was able to do it with mods and division. Thank you all for the responses, it really helped me to open up and think about the problem from different angles.
Here's a hint:
echo ${string:0:3}
echo ${#string}
Edit (2011-02-11):
Here's my solution. I added some additional parameters with defaults.
rotate-string ()
{
local s=${1:-1} p=${2:--1} w=${3:-8} c=${4:-0} r l
printf -vr '%0*d' $w 0 # save $w zeros in $r
r=${r//0/$c}$s # change the zeros to the character in $c, append the string
r=${r: -w} # save the last $w characters of $r
l=${r: -p%w} # get the last part of $r ($p mod %w characters)
echo "$l${r::w-${#l}}" # output the Last part on the Left and the Right part which starts at the beginning and goes for ($w minus the_length_of_the_Left_part) characters
}
usage: rotate-string string positions-to-rotate width fill-character
example: rotate-string abc -4 9 =
result: ==abc====
Arguments can be omitted starting from the end and these defaults will be used:
fill-character: "0"
width: 8
positions-to-rotate: -1
string: "1"
More examples:
$ rotate-string
00000010
$ rotate-string 123 4
01230000
Fun stuff:
$ for i in {126..6}; do printf '%s\r' "$(rotate-string Dennis $i 20 .)"; sleep .05; done; printf '\n'
$ while true; do for i in {10..1} {1..10}; do printf '%s\r' "$(rotate-string : $i 10 .)"; sleep .1; done; done
$ while true; do for i in {40..2} {2..40}; do printf '%s\r' "$(rotate-string '/\' $i 40 '_')"; sleep .02; done; done
$ d=0; while true; do for i in {1..10} {10..1}; do printf '%s\r' "$(rotate-string $d $i 10 '_')"; sleep .02; done; ((d=++d%10)); done
$ d=0; while true; do for i in {1..10}; do printf '%s\r' "$(rotate-string $d $i 10 '_')"; sleep .2; ((d=++d%10)); done; done
$ shape='▁▂▃▄▅▆▇█▇▆▅▄▃▂▁'; while true; do for ((i=1; i<=COLUMNS; i++)); do printf '%s\r' "$(rotate-string "$shape" $i $COLUMNS ' ')"; done; done
In the absence of control structures, you need to use recursion, with index values as "choice selections", which is how functional programming often works.
#!/bin/sh
#
# cshift NUMBER N
cshift() {
let num=10#$1
num=`printf '%09d' $num`
lshift="${num:1:8}${num:0:1}"
rshift="${num:8:1}${num:0:8}"
next=( "cshift $lshift $(($2 + 1))" "echo $num" "cshift $rshift $(( $2 - 1 ))" )
x=$(( $2 == 0 ? 1 : $2 < 0 ? 0 : 2 ))
eval "${next[x]}"
}
cshift $1 $2
and, the testing:
$ for ((i=-9;i<=9;i++)); do cshift 12345 $i ; done
000012345
500001234
450000123
345000012
234500001
123450000
012345000
001234500
000123450
000012345
500001234
450000123
345000012
234500001
123450000
012345000
001234500
000123450
000012345
You can also do some math on the indexes and avoid the recursion, but I don't mind making the computer work harder so I don't have to. It's easy to think of how to do the shift by one in either direction, and then I use an evaluated choice that is selected by the signum of the shift value, outputting a value and stopping when the shift value is zero.

Resources