Generating a random password; why isn't this portable?

It's your locale and tr problem.

Currently, GNU tr fully supports only single-byte characters. So in locales using multibyte encodings, the output can be weird:

$ </dev/urandom LC_ALL=vi_VN.tcvn tr -dc '[:print:]' | head -c 64
`�pv���Z����c�ox"�O���%�YR��F�>��췔��ovȪ������^,<H ���>

The shell will print multi-byte characters correctly, but GNU tr will remove bytes it think non-printable.

If you want it to be stable, you must set the locale:

$ </dev/urandom LC_ALL=C tr -dc '[:print:]' | head -c 64
RSmuiFH+537z+iY4ySz`{Pv6mJg::RB;/-2^{QnKkImpGuMSq92D(6N8QF?Y9Co@

Consider instead

$ dd if=/dev/urandom bs=48 count=1 status=none | base64
imW/X60Sk9TQzl+mdS5PL7sfMy9k/qFBWWkzZjbYJttREsYpzIguWr/uRIhyisR7

This has two advantages:

  • You read only 48 bytes from the random device, not ~8KB; if other processes on the same host need random numbers, 8KB drained all at once can be a serious problem. (Yes, arguably nobody should be using the blocking random device, but people do.)

  • The output of base64 contains almost no characters with special meanings. (For none at all, tack | tr +/ -_ on the end, and (as in the example) make sure the number of bytes input to base64 is a multiple of 3.)

A password generated this way has exactly 384 bits of entropy, which is somewhat less than what you were doing (log2 9664 ≈ 421.4), but more than enough for most purposes (256 bits of entropy is safely in "still guessing when the Sun burns out" territory except for RSA keys, AFAIK).


Other people already pointed out that locale determines what [:print:] means. However, not all printable characters are suitable for passwords (not even in ascii). You really don't want spaces, tabs, and #$%^? in your password - it's not just difficult to remember, it's also potentially dangerous to the underlying authentication system, may be impossible to enter in an input field, and so on. In this case, you should just manually select "sane" characters:

LC_ALL=C </dev/urandom tr -dc '[:alnum:]_' | head -c 64

or simply

</dev/urandom tr -dc 'A-Za-z0-9_' | head -c 64

Or even better, use base64 as suggested in other answers.