43

I am trying to write a shell script. The idea is to select a single line at random from text file and display it as an Ubuntu desktop notification.

But I want different lines to be selected each time I execute the script. Is there any solution to do this? I don't want the entire script. Just that simple thing only.

Eliah Kagan
  • 119,640
Anandu M Das
  • 2,303

8 Answers8

62

You can use shuf utility to print random lines from file

$ shuf -n 1 filename

-n : number of lines to print

Examples:

$ shuf -n 1 /etc/passwd

git:x:998:998:git daemon user:/:/bin/bash

$ shuf -n 2 /etc/passwd

avahi:x:84:84:avahi:/:/bin/false
daemon:x:2:2:daemon:/sbin:/bin/false
αғsнιη
  • 36,350
aneeshep
  • 30,949
19

You can also use sort command to get random line from the file.

sort -R filename | head -n1
Seth
  • 59,332
g_p
  • 19,034
  • 6
  • 59
  • 69
9

Just for fun, here is a pure bash solution which doesn't use shuf, sort, wc, sed, head, tail or any other external tools.

The only advantage over the shuf variant is that it's slightly faster, since it's pure bash. On my machine, for a file of 1000 lines the shuf variant takes about 0.1 seconds, while the following script takes about 0.01 seconds ;) So while shuf is the easiest and shortest variant, this is faster.

In all honesty I would still go for the shuf solution, unless high efficiency is an important concern.

#!/bin/bash

FILE=file.txt

# get line count for $FILE (simulate 'wc -l')
lc=0
while read -r line; do
 ((lc++))
done < $FILE

# get a random number between 1 and $lc
rnd=$RANDOM
let "rnd %= $lc"
((rnd++))

# traverse file and find line number $rnd
i=0
while read -r line; do
 ((i++))
 [ $i -eq $rnd ] && break
done < $FILE

# output random line
printf '%s\n' "$line"
Malte Skoruppa
  • 13,846
  • 5
  • 58
  • 65
5

Say you have file notifications.txt. We need to count total number of lines, to determine range of random generator:

$ cat notifications.txt | wc -l

Lets write to variable:

$ LINES=$(cat notifications.txt | wc -l)

Now to generate number from 0 to $LINE we will use RANDOM variable.

$ echo $[ $RANDOM % LINES]

Lets write it to variable:

$  R_LINE=$(($RANDOM % LINES))

Now we only need to print this line number:

$ sed -n "${R_LINE}p" notifications.txt

About RANDOM:

   RANDOM Each time this parameter is referenced, a random integer between
          0 and 32767 is generated.  The sequence of random numbers may be
          initialized by assigning a value to RANDOM.  If RANDOM is unset,
          it  loses  its  special  properties,  even if it is subsequently
          reset.

Be sure your file have less then 32767 line numbers. See this if you need bigger random generator that works out of the box.

Example:

$ od -A n -t d -N 3 /dev/urandom | tr -d ' '
c0rp
  • 10,010
3

Here's a Python script that selects a random line from input files or stdin:

#!/usr/bin/env python
"""Usage: select-random [<file>]..."""
import random

def select_random(iterable, default=None, random=random):
    """Select a random element from iterable.

    Return default if iterable is empty.
    If iterable is a sequence then random.choice() is used for efficiency instead.
    If iterable is an iterator; it is exhausted.
    O(n)-time, O(1)-space algorithm.
    """
    try:
        return random.choice(iterable) # O(1) time and space
    except IndexError: # empty sequence
        return default
    except TypeError: # not a sequence
        return select_random_it(iter(iterable), default, random.randrange)

def select_random_it(iterator, default=None, randrange=random.randrange):
    """Return a random element from iterator.

    Return default if iterator is empty.
    iterator is exhausted.
    O(n)-time, O(1)-space algorithm.
    """
    # from https://stackoverflow.com/a/1456750/4279
    # select 1st item with probability 100% (if input is one item, return it)
    # select 2nd item with probability 50% (or 50% the selection stays the 1st)
    # select 3rd item with probability 33.(3)%
    # select nth item with probability 1/n
    selection = default
    for i, item in enumerate(iterator, start=1):
        if randrange(i) == 0: # random [0..i)
            selection = item
    return selection

if __name__ == "__main__":
    import fileinput
    import sys

    random_line = select_random_it(fileinput.input(), '\n')
    sys.stdout.write(random_line)
    if not random_line.endswith('\n'):
        sys.stdout.write('\n') # always append newline at the end

The algorithm is O(n)-time, O(1)-space. It works for files larger than 32767 lines. It doesn't load input files into memory. It reads each input line exactly once i.e., you can pipe arbitrary large (but finite) content into it. Here's an explanation of the algorithm.

jfs
  • 4,078
1
awk 'BEGIN{srand()}{a[NR]=$0}END{x=int(rand()*NR)+1; print a[x]}' notifications.txt
ufopilot
  • 111
1

I'm impressed by the work that Malte Skoruppa and others did, but here is a much simpler "pure bash" way to do it:

IFS=$'\012'
# set field separator to newline only
lines=( $(<test5) )
# slurp entire file into an array
numlines=${#lines[@]}
# count the array elements
num=$(( $RANDOM$RANDOM$RANDOM % numlines ))
# get a (more-or-less) random number within the correct range
line=${lines[$num]}
# select the element corresponding to the random number
echo $line
# display it

As some have noted, $RANDOM is not random. However, the file size limit of 32767 lines is overcome by stringing $RANDOMs together as needed.

Wastrel
  • 201
0
awk '
BEGIN { srand() }
1/NR >= rand() { line = $0 }
END { print line }'
andrew.46
  • 39,359