Подтвердить что ты не робот

Лучший способ выбрать случайный файл из каталога в оболочке script

Каков наилучший способ выбрать случайный файл из каталога в оболочке script?

Вот мое решение в Bash, но мне было бы очень интересно использовать более портативную (не GNU) версию для использования в Unix.

dir='some/directory'
file=`/bin/ls -1 "$dir" | sort --random-sort | head -1`
path=`readlink --canonicalize "$dir/$file"` # Converts to full path
echo "The randomly-selected file is: $path"

У кого-нибудь есть другие идеи?

Изменить: lhunath дает хорошее представление о разборе ls. Я предполагаю, что дело доходит до того, хотите ли вы быть переносным или нет. Если у вас есть GNU findutils и coreutils, вы можете:

find "$dir" -maxdepth 1 -mindepth 1 -type f -print0 \
  | sort --zero-terminated --random-sort \
  | sed 's/\d000.*//g/'

Ну, это было весело! Также он соответствует моему вопросу лучше, так как я сказал "случайный файл". Впрочем, в наши дни трудно представить, что Unix-система развернута там, где установлен GNU, но не Perl 5.

4b9b3361

Ответ 1

files=(/my/dir/*)
printf "%s\n" "${files[RANDOM % ${#files[@]}]}"

И не анализировать ls. Прочитайте http://mywiki.wooledge.org/ParsingLs

Edit: Удача в поиске решения bash, надежного. Большинство будет ломаться для определенных типов имен файлов, таких как имена файлов с пробелами или символами новой строки или тире (это практически невозможно в чистом sh). Чтобы сделать это правильно без bash, вам нужно полностью перейти на awk/perl/python/... без конвейера, который будет выводиться для дальнейшей обработки или такого.

Ответ 2

Является ли "шуф" не переносимым?

shuf -n1 -e /path/to/files/*

или найдите, если файлы находятся глубже одного каталога:

find /path/to/files/ -type f | shuf -n1

это часть coreutils, но вам понадобится 6.4 или новее, чтобы получить ее... поэтому RH/CentOS не включает ее.

Ответ 3

Что-то lile "

let x="$RANDOM % ${#file}"
echo "The randomly-selected file is ${path[$x]}"

$RANDOM в bash - это специальная переменная, которая возвращает случайное число, а затем использует модульное деление для получения действительного индекса, а затем индексирует его в массив.

Ответ 4

# ******************************************************************
# ******************************************************************
function randomFile {
  tmpFile=$(mktemp)

  files=$(find . -type f > $tmpFile)
  total=$(cat "$tmpFile"|wc -l)
  randomNumber=$(($RANDOM%$total))

  i=0
  while read line;  do
    if [ "$i" -eq "$randomNumber" ];then
      # Do stuff with file
      amarok $line
      break
    fi
    i=$[$i+1]
  done < $tmpFile
  rm $tmpFile
}

Ответ 5

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

Потому что, если у вас есть случайное число между 1 и N, вы можете использовать head -$N | tail, чтобы вырезать где-то посередине. К сожалению, я не знаю портативного способа сделать это только с оболочкой. Если у вас есть Python или Perl, вы можете легко использовать их случайную поддержку, но AFAIK, нет стандартной команды rand(1).

Ответ 6

Я думаю, что Awk - хороший инструмент для получения случайного числа. Согласно Advanced Bash Guide, Awk - хорошая замена случайных чисел для $RANDOM.

Вот версия вашего script, которая позволяет избежать Bash -ism и инструментов GNU.

#! /bin/sh

dir='some/directory'
n_files=`/bin/ls -1 "$dir" | wc -l | cut -f1`
rand_num=`awk "BEGIN{srand();print int($n_files * rand()) + 1;}"`
file=`/bin/ls -1 "$dir" | sed -ne "${rand_num}p"`
path=`cd $dir && echo "$PWD/$file"` # Converts to full path.  
echo "The randomly-selected file is: $path"

Он наследует проблемы, о которых говорили другие ответы, если файлы содержат символы новой строки.

Ответ 7

files=(/my/dir/*) printf "%s\n" "${files[RANDOM % ${#files}]}"

Ваша идея почти сработала, но мне пришлось добавить [@]

files=(/my/dir/*) printf "%s\n" "${files[RANDOM % ${#files[@]}]}"

Ответ 8

Новые строки в именах файлов можно избежать, выполнив следующее в Bash:

#!/bin/sh

OLDIFS=$IFS
IFS=$(echo -en "\n\b")

DIR="/home/user"

for file in $(ls -1 $DIR)
do
    echo $file
done

IFS=$OLDIFS

Ответ 9

Вот фрагмент оболочки, который опирается только на функции POSIX и справляется с произвольными именами файлов (но пропускает точечные файлы из выделения). Случайный выбор использует awk, потому что все вы получаете в POSIX. Это очень плохой генератор случайных чисел, поскольку awk RNG засевается текущим временем в секундах (поэтому он легко предсказуем и возвращает тот же самый выбор, если вы вызываете его несколько раз в секунду).

set -- *
n=$(echo $# | awk '{srand(); print int(rand()*$0) + 1}')
eval "file=\$$n"
echo "Processing $file"

Если вы не хотите игнорировать файлы точек, код генерации имени файла (set -- *) должен быть заменен чем-то более сложным.

set -- *; [ -e "$1" ] || shift
set .[!.]* "[email protected]"; [ -e "$1" ] || shift
set ..?* "[email protected]"; [ -e "$1" ] || shift
if [ $# -eq 0]; then echo 1>&2 "empty directory"; exit 1; fi

Если у вас есть OpenSSL, вы можете использовать его для генерации случайных байтов. Если у вас нет, но ваша система имеет /dev/urandom, замените вызов на openssl на dd if=/dev/urandom bs=3 count=1 2>/dev/null. Вот фрагмент, который устанавливает n в случайное значение между 1 и $#, стараясь не вводить смещение. Этот фрагмент предполагает, что $# не более 2 ^ 23-1.

while
  n=$(($(openssl rand 3 | od -An -t u4) + 1))
  [ $n -gt $((16777216 / $# * $#)) ]
do :; done
n=$((n % $#))

Ответ 10

BusyBox (используется на встроенных устройствах) обычно настроен на поддержку $RANDOM, но у него нет bash -строчных массивов или sort --random-sort или shuf. Отсюда следует следующее:

#!/bin/sh
FILES="/usr/bin/*"
for f in $FILES; do  echo "$RANDOM $f" ; done | sort -n | head -n1 | cut -d' ' -f2-

Примечание: "-" в cut -f2-; это необходимо для предотвращения обрезки файлов, содержащих пробелы (или любой разделитель, который вы хотите использовать).

Он не будет корректно обрабатывать имена файлов со встроенными символами новой строки.

Ответ 11

Поместите каждую строку вывода из команды 'ls' в ассоциативный массив с именем line, а затем выберите один из них, например...

ls | awk '{ line[NR]=$0 } END { print line[(int(rand()*NR+1))]}'