92

I would like to generate a random filename in unix shell (say tcshell). The filename should consist of random 32 hex letters, e.g.:

c7fdfc8f409c548a10a0a89a791417c5

(to which I will add whatever is neccesary). The point is being able to do it only in shell without resorting to a program.

R S
  • 11,359
  • 10
  • 43
  • 50

14 Answers14

139

Assuming you are on a linux, the following should work:

cat /dev/urandom | tr -cd 'a-f0-9' | head -c 32

This is only pseudo-random if your system runs low on entropy, but is (on linux) guaranteed to terminate. If you require genuinely random data, cat /dev/random instead of /dev/urandom. This change will make your code block until enough entropy is available to produce truly random output, so it might slow down your code. For most uses, the output of /dev/urandom is sufficiently random.

If you on OS X or another BSD, you need to modify it to the following:

cat /dev/urandom | env LC_CTYPE=C tr -cd 'a-f0-9' | head -c 32
fmark
  • 57,259
  • 27
  • 100
  • 107
  • 1
    @LukeN: Yours was good. And technically, this solution is not guaranteed to terminate :) – President James K. Polk May 08 '10 at 15:01
  • 1
    This solution was actually doing weird things for me, as it appended a white-backgrounded "%" sign after the actual random hash, but because my shell is generally behaving strange on some occasions, I didn't want to make this look bad before it was accepted :) – LukeN May 08 '10 at 15:14
  • 1
    I tried this on a Mac, which has a /dev/urandom. Executing the command in a bash shell causes an error - 'tr: Illegal byte sequence' – Gareth Stockwell May 09 '10 at 08:09
  • 5
    I think the problem here is that BSD and Mac's interpret the string as being multibyte instead of single byte. I haven't got a machine to try this on, so report back here if this works: cat /dev/urandom | env LC_CTYPE=C tr -cd 'a-f0-9' | head -c 32 – fmark May 09 '10 at 13:32
  • 1
    @fmark: your modified version works perfectly on my Snow Leopard bash. – President James K. Polk May 09 '10 at 13:45
  • 3
    Looks like another useless use of cat :-) – Jens May 30 '12 at 19:56
  • 1
    @LukeN You may have figured this out by now, 5 years later, but that '%' just means the line ended without a newline character. http://unix.stackexchange.com/a/167600/105858 – John P Jan 27 '17 at 16:24
  • 1
    Even though I know it strays from the literal constraints of the question, I still want to record this here. For my purposes, just change the f to z, and 32 to 9 (or whatever length of filename you desire) ... beautiful. – GG2 Jun 29 '18 at 03:49
50

why do not use unix mktemp command:

$ TMPFILE=`mktemp tmp.XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX` &&  echo $TMPFILE
tmp.MnxEsPDsNUjrzDIiPhnWZKmlAXAO8983
Oleg Razgulyaev
  • 5,757
  • 4
  • 28
  • 28
  • 6
    In this case you will not only generate the string, but create a file also. – Igor Chubin Nov 15 '13 at 14:57
  • 5
    some implementations have a `--dry-run` flag to prevent a file being created. That of course opens a possible race condition. – glenn jackman Jul 21 '14 at 19:54
  • While it is not a big deal, this utility makes an extra IO check to test whether the XXXX file exists on disk (just a stat) even when --try-tun is specified. Just a minor consideration and maybe an insignificant trade off for a great convenience – oᴉɹǝɥɔ Dec 16 '22 at 19:01
23

One command, no pipe, no loop:

hexdump -n 16 -v -e '/1 "%02X"' -e '/16 "\n"' /dev/urandom

If you don't need the newline, for example when you're using it in a variable:

hexdump -n 16 -v -e '/1 "%02X"' /dev/urandom

Using "16" generates 32 hex digits.

Dennis Williamson
  • 346,391
  • 90
  • 374
  • 439
10

uuidgen generates exactly this, except you have to remove hyphens. So I found this to be the most elegant (at least to me) way of achieving this. It should work on linux and OS X out of the box.

uuidgen | tr -d '-'
danran
  • 101
  • 1
  • 4
8

As you probably noticed from each of the answers, you generally have to "resort to a program".

However, without using any external executables, in Bash and ksh:

string=''; for i in {0..31}; do string+=$(printf "%x" $(($RANDOM%16)) ); done; echo $string

in zsh:

string=''; for i in {0..31}; do string+=$(printf "%x" $(($RANDOM%16)) ); dummy=$RANDOM; done; echo $string

Change the lower case x in the format string to an upper case X to make the alphabetic hex characters upper case.

Here's another way to do it in Bash but without an explicit loop:

printf -v string '%X' $(printf '%.2s ' $((RANDOM%16))' '{00..31})

In the following, "first" and "second" printf refers to the order in which they're executed rather than the order in which they appear in the line.

This technique uses brace expansion to produce a list of 32 random numbers mod 16 each followed by a space and one of the numbers in the range in braces followed by another space (e.g. 11 00). For each element of that list, the first printf strips off all but the first two characters using its format string (%.2) leaving either single digits followed by a space each or two digits. The space in the format string ensures that there is then at least one space between each output number.

The command substitution containing the first printf is not quoted so that word splitting is performed and each number goes to the second printf as a separate argument. There, the numbers are converted to hex by the %X format string and they are appended to each other without spaces (since there aren't any in the format string) and the result is stored in the variable named string.

When printf receives more arguments than its format string accounts for, the format is applied to each argument in turn until they are all consumed. If there are fewer arguments, the unmatched format string (portion) is ignored, but that doesn't apply in this case.

I tested it in Bash 3.2, 4.4 and 5.0-alpha. But it doesn't work in zsh (5.2) or ksh (93u+) because RANDOM only gets evaluated once in the brace expansion in those shells.

Note that because of using the mod operator on a value that ranges from 0 to 32767 the distribution of digits using the snippets could be skewed (not to mention the fact that the numbers are pseudo random in the first place). However, since we're using mod 16 and 32768 is divisible by 16, that won't be a problem here.

In any case, the correct way to do this is using mktemp as in Oleg Razgulyaev's answer.

Dennis Williamson
  • 346,391
  • 90
  • 374
  • 439
  • 1
    Thank you Dennis, it helped me.. I just set the string to be empty, so that the lengh of string is always as expected that appending each time while rerunning the same script.. randomId=$(string="";for i in {0..5}; do string+=$(printf "%x" $(($RANDOM%16)) ); done; echo $string); echo "Random Id value is ${randomId}" – Santosh Kumar Arjunan Sep 03 '18 at 01:47
  • @SantoshKumarA: Thanks for pointing out that the variable needs to be initialized. I added that to my answer. I also add another interesting technique and an explanation for it. – Dennis Williamson Sep 03 '18 at 18:35
  • Not using any calls out of the bash process makes this the best answer for me. In preference to the accepted answer. – Blindleistung Jun 02 '22 at 13:18
6

Tested in zsh, should work with any BASH compatible shell!

#!/bin/zsh

SUM=`md5sum <<EOF
$RANDOM
EOF`

FN=`echo $SUM | awk '// { print $1 }'`

echo "Your new filename: $FN"

Example:

$ zsh ranhash.sh
Your new filename: 2485938240bf200c26bb356bbbb0fa32
$ zsh ranhash.sh
Your new filename: ad25cb21bea35eba879bf3fc12581cc9
LukeN
  • 5,590
  • 1
  • 25
  • 33
  • 1
    This will only generate 32768 unique filenames rather than `16^32`. – Dennis Williamson May 30 '12 at 20:13
  • This will also only work on systems that include a `md5sum` binary. FreeBSD and OSX, for example, have a `/sbin/md5` which serves the same purpose but uses different option syntax. – ghoti Oct 21 '14 at 19:59
4

Yet another way[tm].

R=$(echo $RANDOM $RANDOM $RANDOM $RANDOM $RANDOM | md5 | cut -c -8)
FILENAME="abcdef-$R"
reto
  • 16,189
  • 7
  • 53
  • 67
  • the program available on most systems is md5sum. I like this solution, because if you don't need high randomness, you can just use $RANDOM only once, making this the briefest most readable solution. –  Apr 14 '14 at 08:16
  • a) If you have md5sum you can replace md5 with md5sum and update the cut accordingly. b) Using only one $RANDOM is dangerous, it produces only values between 0 and 32767, thats much to short, depending on the application. – reto Apr 14 '14 at 15:08
3

This answer is very similar to fmarks, so I cannot really take credit for it, but I found the cat and tr command combinations quite slow, and I found this version quite a bit faster. You need hexdump.

hexdump -e '/1 "%02x"' -n32 < /dev/urandom
srclosson
  • 191
  • 1
  • 1
  • 7
3

Another thing you can add is running the date command as follows:

date +%S%N

Reads nonosecond time and the result adds a lot of randomness.

pfnuesel
  • 14,093
  • 14
  • 58
  • 71
3

The first answer is good but why fork cat if not required.

tr -dc 'a-f0-9' < /dev/urandom | head -c32
Josiah DeWitt
  • 1,594
  • 13
  • 15
  • The question, although gives an example of tcsh, is in a context of a generic UNIX shell. Your version is specific to shell extensions. – oᴉɹǝɥɔ Dec 16 '22 at 18:47
2

Grab 16 bytes from /dev/random, convert them to hex, take the first line, remove the address, remove the spaces.

head /dev/random -c16 | od -tx1 -w16 | head -n1 | cut -d' ' -f2- | tr -d ' '

Assuming that "without resorting to a program" means "using only programs that are readily available", of course.

Thomas
  • 174,939
  • 50
  • 355
  • 478
  • I tested this out. Its a lot slower then the other solutions, but does work. – aarona May 08 '10 at 11:33
  • Yeah, it's a fairly long pipe for something simple like that. I'd hoped for some more flags on `od`, but couldn't get it to do what I wanted. fmark's answer, though it processes more bytes, might actually be faster. – Thomas May 08 '10 at 11:36
  • 1
    It would be faster if it used /dev/urandom rather than /dev/random – fmark May 08 '10 at 11:38
  • fmark is right, it could be the /dev/random part here, because /dev/random is a truly random number generator and will block when there's no more entropy :) – LukeN May 08 '10 at 11:48
  • This solution is definitely the most portable one! It even works on the latest Android (except you should replace `-w16` with `-N16`). – user1643723 Dec 26 '16 at 13:48
2

If you have openssl in your system you can use it for generating random hex (also it can be -base64) strings with defined length. I found it pretty simple and usable in cron in one line jobs.

 openssl rand -hex 32
 8c5a7515837d7f0b19e7e6fa4c448400e70ffec88ecd811a3dce3272947cb452
Alex
  • 5,728
  • 5
  • 20
  • 20
1

Hope to add a (maybe) better solution to this topic.

Notice: this only works with bash4 and some implement of mktemp(for example, the GNU one)

Try this

fn=$(mktemp -u -t 'XXXXXX')
echo ${fn/\/tmp\//}

This one is twice as faster as head /dev/urandom | tr -cd 'a-f0-9' | head -c 32, and eight times as faster as cat /dev/urandom | tr -cd 'a-f0-9' | head -c 32.

Benchmark:

With mktemp:

#!/bin/bash
# a.sh
for (( i = 0; i < 1000; i++ ))
do
    fn=$(mktemp -u -t 'XXXXXX')
    echo ${fn/\/tmp\//} > /dev/null
done

time ./a.sh 
./a.sh  0.36s user 1.97s system 99% cpu 2.333 total

And the other:

#!/bin/bash
# b.sh
for (( i = 0; i < 1000; i++ ))
do
    cat /dev/urandom | tr -dc 'a-zA-Z0-9' | head -c 32 > /dev/null
done

time ./b.sh 
./b.sh  0.52s user 20.61s system 113% cpu 18.653 total
罗泽轩
  • 1,603
  • 2
  • 14
  • 19
1

If you are on Linux, then Python will come pre-installed. So you can go for something similar to the below:

python -c "import uuid; print str(uuid.uuid1())"

If you don't like the dashes, then use replace function as shown below

python -c "import uuid; print str(uuid.uuid1()).replace('-','')"
Thyag
  • 1,217
  • 13
  • 14