10,000 Monkeys Typing…with a Unix/sh challenge…

I was testing some code that I wrote for analyzing cryptograms, and decided that the easiest way to do so would be to get some random text, drawn from the letters A-Z. A moments thought yielded this method, without even programming anything:

tr -d -c A-Z < /dev/urandom | dd ibs=10000 count=1

The tr generates the required data, and the dd truncates it to the desired number of characters. But for tidiness, I'd like to have the output broken up so that each line consists of 50 characters, with spaces inserted between every 5 characters (I won't begrudge you if you leave a dangling space at the end of each line). I couldn't figure out a simple way to get that to happen all in one command line and using standard utilities. I can of course write a little Python utility, or even perl, but can anyone think of a clever short way to do this?

Addendum: Tom pointed out something interesting about the command that I listed above: it doesn’t work the way I think it does. Apparently the ibs is the input block size, which dd dutifully allocates, and the count is the number of read system calls that the system issues. For reasons which escape me, it does not try to make sure that it actually received a full input block: it will nicely return short blocks if it finds them, and doesn’t bother retrying to get more. Hence, it works rather erratically when using a pipe as input, particularly when the writes from the upstream process may flush in odd intervals.

16 thoughts on “10,000 Monkeys Typing…with a Unix/sh challenge…

  1. Shannon Nelson

    Sed is a magical tool, and cut comes in handy at the last:

    tr -d -c A-Z < /dev/urandom | dd ibs=10000 count=1 | sed -e 's/\(.\{5\}\)/\1 /g' -e 's/\(.\{60\}\)/\1\n/g' | cut -c 1-59

  2. Shannon Nelson

    Of course, this points out one of the problems with specs. Note that I didn’t follow your specs to the letter, I interpreted them and came up with an answer that made sense to me: you asked for lines of 50 characters, and I gave you lines of 59 characters. Was I wrong in not following the given spec?

  3. VK5FNET

    #!/usr/bin/perl
    #use strict;
    #use warnings;

    my $limit = 250; # ten lines of five groups of five
    my @cypher; # where we put the plain text pad
    while () { # read line by line from STDIN
    my @chars = split(undef, $_); # split the line into a list of characters
    #print “|”;
    for my $char (@chars) {
    if ($char =~ m/[A-Z]/ ) { # if the character is A-Z
    push @cypher, $char;
    #print “.”;
    if ($limit <= scalar(@cypher)) {
    #print "\n";
    #print scalar(@cypher);
    my $offest = 0;
    while (@cypher) {
    for (1..5) { # GROUP
    print " ";
    for (1..5) { #LETTERS
    print pop @cypher;
    }
    }
    print "\n";
    }
    exit;
    }
    }
    }
    #print scalar(@cypher)."\n";
    }

  4. VK5FNET

    Ok, how about i tidy that up a bit. So 10 groups of 5 characters, totalling 10,000. All comments and debug removed. No doubt there are smaller and faster ways to do this in perl, but you need to be able to read it first to understand it…

    This code reads the standard input with the while () then converts each line to an array and interates over the array to pull out the characters we care about then when our array is full, print it out.

    perl -e ‘my $limit = 10_000; my @cypher;while (){my @chars=split(undef, $_);for my $char (@chars){if ($char =~ m/[A-Z]/ ){push @cypher, $char;if($limit<=scalar(@cypher)){my $offest = 0;while(@cypher){for(1..10){print " ";for(1..5){print pop @cypher;}}print "\n";}exit;}}}}' < /dev/urandom

  5. Tom Duff

    This works on Linux but not on Mac OS X (no -w flag to od):

    tr -d -c A-Z < /dev/urandom |
    dd ibs=1 count=10000 |
    od -cvw50 |
    sed 's/^[0-7]* *//;s/ //g;s/…../& /g'

    The trick is to find a command that doesn't look for newlines in its input; od is the obvious choice. Unfortunately od's -w flag is non-posix.

    dd ibs=10000 doesn't actually work how you'd expect when its input is a pipe, for complicated reasons.

  6. Alan Yates

    tr -d -c A-Z < /dev/urandom | fold -w 50 | sed 's/\(.\{5\}\)/\1 /g'

    Truncate to the desired length… I am assuming you mean 50 non-space chars?

    If you dislike sed you can try this abomination:

    tr -d -c A-Z < /dev/urandom | fold -w 5 | tr '12' ' ' | fold -w 60

  7. VK5FNET

    hmmm, comment code is striping the < and > characters.
    so make sure the while statement looks like this;

    while (<>) {

    anyhow, shared the challenge at lunch with other programmers. here is another solution.

    perl -e ‘$limit=shift; $group=5; $i=0; while($i<$limit) { read STDIN, $b, 1; $o=ord $b; next if $o>234; print chr(65+($o % 26)), (++$i % $group ? “” : ” “) } print “\n”‘ 150 < /dev/urandom | fold -w 60

  8. VK5FNET

    Alan, the fold command is nice, and I’m assuming that there should be a backslash in front of of the 12 there.

    So that its the form-feed char, not the carriage-return 13, or line-feed 10 char?

    Or is that in 12 in octal?

  9. kiwimonster

    sed is for wimps. hmm.. how to make it clearer, while still being obtuse…

    tr -d -c A-Z < /dev/urandom | fold -w 5 | xargs -n 10 echo | head -200

  10. Tom Duff

    As usual for this sort of thing, is comes out shorter & easier to read in C than in Perl:

    #include <stdio.h>
    #include <stdlib.h>
    int main(int argc, char *argv[]){
    int c, i, n=argc<2?10000:atoi(argv[1]);
    if(n<0) fprintf(stderr, "%s: %s?\n", argv[0], argv[1]), exit(1);
    FILE *f=fopen("/dev/urandom", "r");
    if(f==0) perror("/dev/urandom"), exit(1);
    for(i=0;i!=n;i++){
    do c=getc(f); while(c<‘A’ || ‘Z'<c);
    putchar(c);
    if(i%5==4) putchar(i%50==49?’\n’:’ ‘);
    }
    if(i%50!=0) putchar(‘\n’);
    return 0;
    }

    (Hope this formats right.)

  11. Tom Duff

    Hmm, maybe this formats correctly:

    #include <stdio.h>
    #include <stdlib.h>
    int main(int argc, char *argv[]){
        int c, i, n=argc<2?10000:atoi(argv[1]);
        if(n<0) fprintf(stderr, "%s: %s?\n", argv[0], argv[1]), exit(1);
        FILE *f=fopen("/dev/urandom", "r");
        if(f==0) perror("/dev/urandom"), exit(1);
        for(i=0;i!=n;i++){
            do c=getc(f); while(c<‘A’ || ‘Z'<c);
            putchar(c);
            if(i%5==4) putchar(i%50==49?’\n’:’ ‘);
        }
        if(i%50!=0) putchar(‘\n’);
        return 0;
    }

  12. Pádraig Brady

    dd doesn’t read full blocks by default for backwards compat reasons.
    We added the iflag=fullblock option to coreutils recently, so you can do:

    tr -d -c A-Z < /dev/urandom |
    dd ibs=10000 iflag=fullblock count=1 cbs=50 conv=unblock |
    sed 's/\(.\{5\}\)/\1 /g; s/ $//;'

    Trading a little efficiency for clarity,
    one could replace the `dd` above with:

    fold -w50 | head -n200

  13. rking

    ruby -e ‘204.times do 8.times do 5.times do print (“A”..”Z”).to_a[rand(26)] end; print ” “; end; puts end’

    or a little more idiomatically (and it buys us 4 extra lines by leaving off the ending space):

    ruby -e ‘208.times { puts (1..8).map { (1..5).map { (“A”..”Z”).to_a[rand(26)]}.join }.join ” ” }’

    If I’m going to use shell, it’s:

    pwgen -C0Bs 5 2000 | tr ‘[:lower:]’ ‘[:upper:]’

  14. Pingback: junk code | VK5FJ

Comments are closed.