0

Ok so my problem is as followed, I wish to pass this :-

echo $(($(date +%s%N)/1000000))

Into a variable "a" to it can be added to an array, something like this :-

a=$(($(date +%s%N)/1000000))

The reason I am doing this is a would like 4 random digits to play with (Random numbers). I have produced this bash script to show an example.

#!/bin/bash
for (( c=0; c<=10; c++))
do
        echo $(($(date +%s%N)/1000000))
        sleep .5
done

Which outputs :- (Ignore the first 9 digits)

1622001937610
1622001938249
1622001938758
1622001939267
1622001939774
1622001940282
1622001940790
1622001941299
1622001941807
1622001942315
1622001942823

Now I wanted to add the results of just one instance of this into an array, index from the last 9th digit to receive 4 random digits based on nano second time.

How ever I don't seem to have full grasped the syntax used within bash to achieve the result. Could I possible call date +%s%N)/1000000 straight into an array? As my idea was to create and empty array and then just append the result into the array, and index from the 9th number. And pass the result into a second variable from there I can work on.

Just learning to turn the result of date +%s%N)/1000000 into a variable would be of great help.

Sorry to be a pain. Any thank you in advanced.

7
  • Why do not use /dev/random? May 26, 2021 at 7:07
  • 2
    why not use echo $RANDOM ? (unless you want to focus more on code than result)
    – Archemar
    May 26, 2021 at 7:20
  • 1
    Always /dev/urandom rather than /dev/random
    – roaima
    May 26, 2021 at 8:43
  • 1
    Your a=$(($(date +%s%N)/1000000)) works for me, what problem do you have with it?
    – DonHolgo
    May 26, 2021 at 8:53
  • 1
    If you do $((ns / 1000000)), you drop off the six rightmost digits, that is the micro- and nanoseconds, leaving a millisecond timestamp, which isn't that random. E.g. if we look at the output you have, the three last digits alternate between 2xx and 7xx for a while until it drifts to 3xx and 8xx. You could use $(( ns % 1000000 )) to take the remainder instead, keeping the six lowest-order digits, but then the clock granularity might not be good enough for all of those to be meaningful either.
    – ilkkachu
    May 26, 2021 at 16:17

3 Answers 3

2

My initial approach was to use string processing for the date output to get your required values. The version here uses maths processing (divide by 1000000, modulo 10000) but I've left the alternative commented for you

#!/bin/bash
items=()

random=$(( ($(date +%s%N) / 1000000) % 10000 ))        # Second and milliseconds
# random=$( date +%s%N | grep -oP '....(?=......$)' )

items+=($random)                                       # Append value to array

echo "${items[0]}"                                     # First array value
echo "${items[-1]}"                                    # Last (most recently appended) value
declare -p items                                       # Visual inspection of array elements
2
  • 1
    declare -p varname is handy to inspect the contents of variables (including arrays) May 26, 2021 at 13:50
  • @glennjackman thank you. answer updated
    – roaima
    May 26, 2021 at 15:48
0

You can use the ${var:offset:length} parameter expansion syntax to extract a substring of a value:

$ nanoseconds=$(date +%N)
$ printf '%s\n' "$nanoseconds" "${nanoseconds:2:4}"
785455000
5455

Or, as suggested, using /dev/urandom:

$ tr -dc '[:digit:]' < /dev/urandom | fold -w 4 | head -n 10
8386
9194
3897
8790
4738
1453
4323
9021
6033
8889

Reading that into an array, using the bash mapfile command:

$ mapfile -t numArray < <(tr -dc '[:digit:]' < /dev/urandom | fold -w 4 | head -n 10)
$ declare -p numArray
declare -a numArray=([0]="2851" [1]="9684" [2]="5823" [3]="5206" [4]="3208" [5]="2914" [6]="0395" [7]="4128" [8]="1876" [9]="5691")
0

If you run date +%s%N, the output looks something like this:

1622046533072036066
ssssssssssmmmuuunnn

with the right-hand digits indicating the smaller units. (m/u/n for milli-/micro-/nanoseconds.)

If you the divide that by 1000000, as $(( ... / 1000000)) does, you remove the six rightmost digits, leaving only the seconds and milliseconds.

Those are not very random. E.g. in your test run, the consecutive output numbers increase by a somewhat consistent 508, which in milliseconds closely matches the 0.5 seconds you asked for.

You'd likely get more random values if you instead kept the rightmost digits, and dropped the leading ones, e.g. with the modulo operator, $(( ... % 1000000)). Though it's possible that the lowest digits aren't very random either, if the system doesn't have clocks with a fine enough granularity.

If you keep only the low digits, you don't really need to have date output the full seconds, but could instead use just +%N, except that the nanoseconds value is always zero-padded to 9 digits, and Bash treats numbers starting with zeroes as octal, so e.g. 092345678 would produce an error. Adding an extra digit to the front would prevent that, but so does adding the seconds value.

On my system, the difference between the values from consecutive iterations of the following loop was roughly in the range of 1470000 to 1560000 ns, (~ 1.5 ms / iteration) so I probably wouldn't use more than four of the rightmost digits.

#/bin/bash
array=()
prev=$(date +%s%N)
for ((i=0; i < 100; i++)); do 
        this=$(date +%s%N)
        # diff=$(( (this - prev) % 1000000000))
        diff=$(( (this - prev) % 10000))
        printf "$prev $this %04d\n" "$diff"
        array+=("$diff")
        prev=$this
done

Instead of arithmetic, one could just treat the output from date as a string, and chop some characters off. This would leave the last 4 characters:

a=$(date +%N)
a=${a: -4}

Then again, one could consider other ways to produce random numbers, e.g. shuf from GNU coreutils, which isn't limited to just shuffling, but can also pick values with repetition. E.g. this would print 20 numbers, 4 digits each:

shuf -i 0000-9999 -r -n 20

or with some hoops to get zero-padding:

shuf -i 10000-19999 -r -n 20 |sed -e 's/^1//'

You can read the output to an array with readarray:

readarray array < <(shuf -i 0000-9999 -r -n 20)

Not surprisingly, shuf is hideously faster than looping in the shell over calls to date. You could also use a smaller range, e.g. -i 0-9 to get single-digit numbers.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .