16 февраля 2011 г.

[bash] Выбор случайного файла из директории.

Как-то раз, мне понадобился скрипт который бы сам выбирал рандомные файлы из директории. Собственно, я написал его и сейчас выложу здесь.

На для начала разберемся как будем это делать. Итак, на входе подается список директорий, в которых лежат нужные нам файлы. Собираем файлы и пихаем в массив. Перемешиваем все файлы в массиве (тогда при последовательном обходе они станут случайными). И все. Очень просто. На самом деле мой скрипт немного сложнее, но весь алгоритм такой.

Вот код:
#!/bin/bash

onSIGINT() { # создаем функцию которая будет завершать вечный цикл
    echo "SIGINT received, bye bye!"
    exit 0
}
trap 'onSIGINT' INT # трап, вызывающий эту функцию на SIGINT

regexType="posix-egrep" # переменная указывающая на то, какие регулярные выражения будет использовать утилита find
regex=".*\.(mp3|flac)$" # сам шаблон регулярного выражения
depth="u" # глубина поиска. u [unlimited] - не ограничена, может быть числом

if [[ $# -eq 0 ]]; then # проверка. Если не получили аргументов, то выводим хелп и выходим
    echo "Usage: $0 [-d DEPTH] DIR1 [DIR2 [DIR3 [...]]]"
    exit 0
fi

index=0
for i in `seq 1 $#`; do # цикл обработки аргументов командной строки
    arg=${!i}
    if [[ $bNextDepth -eq 1 ]]; then 
        bNextDepth=0
        if [[ $arg =~ ^[0-9]+$ ]]; then # по сути проверка - число ли depth или нет
            depth=$arg
            continue
        else
            echo "Missing value for -d."  # ошибка. Юзер написал -d но не написал значение для него
            exit 1
        fi
    fi
    if [ "$arg" = "-d" ]; then
        bNextDepth=1
        if [[ $# -eq $i ]]; then
            echo "Missing value for -d."
            exit 1
        fi
        continue
    fi
    if [ ! -d "$arg" ]; then # проверяем, действительно ли нам подали директорию?
        echo "Directory $arg does not exists."
    else
        dirList[$index]=$arg # сохраняем ее в массив
        let "index++"
    fi
done # конец обработки аргументов командной строки

if [[ ${#dirList[@]} -eq 0 ]]; then # если юзер глупый и не ввел не одну правильную директорию выходим, сообщая ошибку
    echo "No one correct directory has been found."
    exit 2
fi

index=0
if [ "$depth" != "u" ]; then # если у нас в depth число
    depthArg="-maxdepth $depth"
fi

while read line
do
    filesArray[$index]="$line"
    let "index++"
done < <(find "${dirList[@]}" $depthArg -regextype "$regexType" -iregex "$regex" -type f) # когда я впервые увидел эту строку в одном примере, желание писать скрипты на баше у меня почти отпало. Спасибо людям на канале #gentoo-ru которые мне объяснили ее смысл. А про find я расскажу ниже.

filesNum=${#filesArray[@]}
echo "Found $filesNum files."

let "filesNum--" # уменьшаем на 1. Потому что for в баше (в отличии от Си) выберет последний элемент тоже.
while [ true ]; do
    for i in `seq 0 "$filesNum"`; do # цикл который перемешает массив
        let "rndPos = RANDOM % $filesNum"
        temp="${filesArray[$i]}" # в этих 3 строках меняем местами элементы
        filesArray[$i]="${filesArray[$rndPos]}"
        filesArray[$rndPos]="$temp"
    done
    for i in `seq 0 "$filesNum"`; do # собственно, цикл запускающий нашу программу с нужным аргументом
        /usr/bin/mplayer "${filesArray[$i]}"
    done
done

exit 0 # все

find "${dirList[@]}" $depthArg -regextype "$regexType" -iregex "$regex" -type f
  • Здесь ${dirList[@]} - массив корректных директорий который мы составили ранее. 
  • $depthArg (без кавычек) - два слова, первое - аргумент (-maxdepth), второе - его значение. 
  • $regexType - тип регулярного выражения. 
  • $regex - шаблон регулярного выражения по которому мы выбираем файлы из директории. 
  • -type f - указывает find'у то, что нас интересуют только реальные файлы. 
Как вы уже могли заметить, этот скрипт подбирает рандомные файлы mp3 или flac и запускает mplayer от них. Реализовано все так, что, пока все файлы не пройдут, они повторятся не будут (по сути, мы просто воспроизводим все по порядку из массива filesArray, но перед началом вложенного цикла перемешиваем массив случайным образом).
-maxdepth N это параметр указывающий find'у, сколько раз надо проходить во вложенные директории (1 - ни разу не переходить).
Кстати, вместо блока кода     
for i in `seq 0 "$filesNum"`; do
        let "rndPos = RANDOM % $filesNum"
        temp="${filesArray[$i]}" 
        filesArray[$i]="${filesArray[$rndPos]}"
        filesArray[$rndPos]="$temp"
done
можно использовать утилиту shuf. То есть весь этот блок кода заменить на строку:
shuf -e ${filesArray[@]}
Проблема в том, что shuf не входит в POSIX.
Еще можно попробовать делать это через sort -R.

Вот и все.

17 комментариев:

  1. вот тебе задачка покруче: скрипт чтоб делал каталог музыки, музыка разбита на категории и подкатегории и под-подкатегории, количество уровней - разное, надо сделать папку с линками на все групы, но не альбомы

    ОтветитьУдалить
  2. Ты молодец, чувак! Жаль, твой блог мне не попался, когда мы Linux в универе проходили.

    ОтветитьУдалить
  3. Очень хороший блог .. ;)) Я люблю его, потому что вы можете прийти к знаю, что многие вещи; ** поцелуев отправить польских **;



    .......... (░)(░)
    ........(░)(♥)(░)
    ..........(░)(░)
    ............(.|./)
    ....(▒)(▒)..|/)(░)(░)
    ..(▒)(♥)(▒).(░)(♥)(░)
    .....(▒)(▒)..|.(░)(░)
    ..................|/)
    .............¯¯¯¯¯/
    ............. ) ¯¯¯(
    ............./▫◊ ◊▫
    ............/▫▫▫▫▫▫▫▫
    .........../▫▪▫▪▫▪▫▪▫▫
    .......... \_______/

    ОтветитьУдалить
  4. Самый полезный блог из всех, которые я встречал. Пиши еще.

    ОтветитьУдалить
  5. Ну хоть где-то все доступно объясняют!

    ОтветитьУдалить
  6. заставил меня почуствовать блондинкой

    ОтветитьУдалить
  7. ах ты ж шикарный =)
    гдеты раньше был?)))

    ОтветитьУдалить
  8. Хуйня какая-то. Выясняем количество файлов, генерируем число, берем файл с индексом равным этому числу, все!
    Строк пять.

    ОтветитьУдалить
  9. Надо будет поставить на ноутбук иксось.

    ОтветитьУдалить
  10. LaserJet, и будут у нас случаи когда одни и те же файлы проходят чаще чем другие. Кроме того, если в моем скрипте убрать все проверки и фичу с заданием глубины аргументом, то будет тоже 5 строк.

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

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

    ОтветитьУдалить