2016-08-20 4 views
0

Было просто интересно, потому что я взбивал это в прошлом месяце.Лучший способ выбрать случайную запись из args?

#!/usr/bin/bash 

# Collects all of the args, make sure to seperate with ',' 
IN="$*" 

# Takes everything before a ',' and places them each on a single line of tmp file 
echo $IN | sed 's/,/\n/g' > /tmp/pick.a.random.word.or.phrase 

# Obvious vars are obvious 
WORDFILE="/tmp/pick.a.random.word.or.phrase" 

# Pick only one of the vars 
NUMWORDS=1 

## Picks a random line from tmp file 

#Number of lines in $WORDFILE 
tL=`awk 'NF!=0 {++c} END {print c}' $WORDFILE` 

# Expand random 
RANDOM_CMD='od -vAn -N4 -tu4 /dev/urandom' 

for i in `seq $NUMWORDS` 
do 
rnum=$((`${RANDOM_CMD}`%$tL+1)) 
sed -n "$rnum p" $WORDFILE | tr '\n' ' ' 

done 

printf "\n" 

rm /tmp/pick.a.random.word.or.phrase 

В основном я спрашиваю:

  1. мне нужно иметь файл TMP ли?
  2. Есть ли способ сделать это в одной строке с другой программой?
  3. Как сконденсировать как можно больше?
+0

Можете ли вы показать пример того, как вы вызываете команду? Неясно, где цифра запятой в описании. –

+1

Кстати, шаблон, который вы используете с «RANDOM_CMD», подвержен серьезным ошибкам - он не будет корректно обрабатывать команды, содержащие литеральные кавычки, экранированные пробелы и т. Д .; см. [BashFAQ # 50] (http://mywiki.wooledge.org/BashFAQ/050) для описания соответствующей передовой практики. –

+1

Вы также можете рассмотреть возможность замены (нестандартной) команды 'seq' на bash-builtin' for ((i = 0; i

ответ

0

Вы можете использовать shuff, чтобы сократить ваш скрипт и удалить временный файл.

#!/usr/bin/bash 

# Collects all of the args, make sure to seperate with ',' 
IN="$*" 

# Takes everything before a ',' and places them in an array 
words=($(echo $IN | sed 's/,/ /g')) 

# Get random indexi in range: 0, length of array: words 
index=$(shuf -i 0-"${#words[@]}" -n 1) 

# Print the random index 
echo ${words[$index]} 

Если вы не хотите использовать SHUFF, вы можете также использовать $RANDOM:

#!/usr/bin/bash 

# Collects all of the args, make sure to seperate with ',' 
IN="$*" 

# Takes everything before a ',' and places them in an array 
words=($(echo $IN | sed 's/,/ /g')) 

# Print the random index 
echo ${words[$RANDOM % ${#words[@]}]} 
+0

$ {# words} всегда 1. это должно быть $ {# words [@]} – frangio

+0

@frangio: '$ {# words}' не всегда 1. Это длина символов '$ words'; если 'words' является массивом,' $ words' является первым элементом массива. – rici

+0

Ты прав @rici. – frangio

2

Командная строка обработки аргумент, на мой взгляд, странно. Почему бы просто не использовать обычные аргументы командной строки? Это делает задачу тривиальной:

#!/usr/bin/bash 
shuf -en1 "[email protected]" 

Конечно, вы могли бы просто использовать shuf -en1, который только девять нажатий клавиш:

$ shuf -en1 word another_word "random phrase" 
another_word 
$ shuf -en1 word another_word "random phrase" 
word 
$ shuf -en1 word another_word "random phrase" 
another_word 
$ shuf -en1 word another_word "random phrase" 
random phrase 

shuf командной строки флаги:

-e Shuffle command line arguments instead of lines in a file/stdin 
-n1 Produce only the first random line (or argument in this case) 

Если вы действительно настаивают на совместном использовании аргументов, а затем разделяя их запятыми, вы можете использовать следующее. Как и оригинал, он будет проявлять неожиданное поведение, если какое-то слово в аргументах может быть Глобы вспененным, так что я действительно не рекомендую:

#!/usr/bin/bash 
IFS=, read -ra args <<<"$*" 
echo $(shuf -en1 "${args[@]}") 

Первая линия сочетает в себе аргументы, а затем делит результат на запятыми в массив args. (Опция -a для чтения.) Поскольку строка разделяется запятыми, сохраняются пробелы (например, автоматически вставленные конкатенацией аргументов); для удаления пробелов, я разбиваю слово на результат shuf, не цитируя расширение команды.

+0

@charles: Я пытался имитировать поведение в OP, которое я уже назвал «причудливым». Вывод цитат был преднамеренным, но, как вы говорите, подвержен несчастным случаям. – rici

+0

Ahh, справа. Предположим, вы можете использовать 'set -f' для отключения globbing, но тогда код OP расширил globs ...может просто расширить ответ («Если вы действительно настаиваете на совместном использовании аргументов, разделяя их запятыми и расширяя каждое значение после разделения как glob») –

+0

Спасибо, я понятия не имел, что существует программа под названием 'shuf', так что отвечает на вопрос №2 прямо там. –

0

shuf в coreutils делает именно это, но с несколькими аргументами команды вместо одного аргумента, разделенного запятыми.

shuf -n1 -e arg1 arg2 ... 

В опции -n1 предлагается выбрать только один элемент. Опция -e указывает, что элементы будут переданы как аргументы (в отличие от стандартного ввода).

Ваш скрипт должен просто заменить запятые пробелами в $*. Мы можем это сделать, используя замену параметров bash:

#!/usr/bin/bash 
shuf -n1 -e ${*//,/ } 

Это не будет работать с элементами со встроенными пространствами.

+0

Почему бы не 'shuf -n1 -e" $ @ "'? Таким образом, у вас нет проблем со встроенными пространствами (или globs - '*' затем помещает имя каждого файла в текущий каталог в перетасованное множество вещей) –

+0

... Я вижу, что rici уже там. –

+0

Да, это определенно то, что нужно сделать, я просто придерживался формата OP – frangio

0

Не так ли просто, как генерировать число в случайном порядке от 1 до $# и просто эхо-аргумент?Это зависит от того, что у вас есть; ваш комментарий о «сборе аргументов»; убедитесь, что разделить запятыми не ясно, потому что назначение ничего не делает с запятыми - и вы не показываете, как вы вызываете свою команду.

Я просто вырезал генерацию случайных чисел из вопроса: он работает нормально на моем Mac, генерируя значения 42,405,691 и 1,817,261,076 при последовательных прогонах.

n=$(($(od -vAn -N4 -tu4 /dev/urandom) % $# + 1)) 
eval echo "\${$n}" 

Можно даже уменьшить, что к одной строке, если вы действительно были определены:

eval echo "\${$(($(od -vAn -N4 -tu4 /dev/urandom) % $# + 1))}" 

Это использование eval безопасно, так как не предполагает ввод данных пользователем. Скрипт должен проверить, что ему предоставляется хотя бы один аргумент, чтобы предотвратить ошибку с делением на нуль, если $# равен 0. Код выполняет абсолютный минимум перемещения данных - в отличие от решений, которые каким-то образом перетасовывают данные.

Если это упаковано в сценарии random_selection, то я могу запустить:

$ bash random_selection Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec 
Feb 
$ bash random_selection Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec 
Oct 
$ bash random_selection Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec 
Nov 
$ 

Если общее количество аргументов, достаточно большое, что вы бежите из аргументов пространства, то вам нужно подумать еще раз, но ограничение присутствует в существующем коде.

Отбор незначительно смещен в сторону более ранних записей в списке; вам нужно сделать лучшую работу по отказу от случайных чисел, которые очень близки к максимальному значению в диапазоне. Для случайного 32-разрядного значения без знака, если оно больше $# * (0xFFFFFFFF/$#), вы должны сгенерировать еще одно случайное число.

Смежные вопросы