Como posso selecionar arquivos aleatórios de um diretório no bash?

Respostas:

180

Aqui está um script que usa a opção aleatória do GNU sort:

ls |sort -R |tail -$N |while read file; do
    # Something involving $file, or you can leave
    # off the while to just get the filenames
done
Josh Lee
fonte
Legal, não sabia o tipo -R; Eu usei bogosort anteriormente :-p
alex
5
opção sort: invalid - R Tente `sort --help 'para obter mais informações.
2
Parece não funcionar para arquivos que possuem espaços neles.
Houshalter 17/03/19
Isso deve funcionar para arquivos com espaços (o pipeline processa linhas). Não funciona para nomes com nova linha. Somente o uso de "$file", não mostrado, seria sensível aos espaços.
precisa saber é o seguinte
108

Você pode usar shuf(do pacote GNU coreutils) para isso. Apenas alimente uma lista de nomes de arquivos e peça para retornar a primeira linha de uma permutação aleatória:

ls dirname | shuf -n 1
# probably faster and more flexible:
find dirname -type f | shuf -n 1
# etc..

Ajuste o -n, --head-count=COUNTvalor para retornar o número de linhas desejadas. Por exemplo, para retornar 5 nomes de arquivos aleatórios, você usaria:

find dirname -type f | shuf -n 5
Mainframe nórdico
fonte
4
A OP queria selecionar Narquivos aleatórios, portanto, usar 1é um pouco enganador.
precisa saber é
4
Se você tiver nomes de arquivos com novas linhas:find dirname -type f -print0 | shuf -zn1
Hitechcomputergeek
5
e se eu tiver que copiar esses arquivos selecionados aleatoriamente para outra pasta? como executar operações nesses arquivos selecionados aleatoriamente?
Rishabh Agrahari
18

Aqui estão algumas possibilidades que não analisam a saída lse são 100% seguras em relação a arquivos com espaços e símbolos engraçados em seu nome. Todos eles preencherão uma matriz randfcom uma lista de arquivos aleatórios. Essa matriz é facilmente impressa, printf '%s\n' "${randf[@]}"se necessário.

  • Esse arquivo possivelmente produzirá o mesmo arquivo várias vezes e Nprecisa ser conhecido com antecedência. Aqui eu escolhi N = 42.

    a=( * )
    randf=( "${a[RANDOM%${#a[@]}]"{1..42}"}" )

    Esse recurso não está muito bem documentado.

  • Se N não for conhecido antecipadamente, mas você realmente gostou da possibilidade anterior, você pode usá-lo eval. Mas é ruim, e você deve realmente garantir que Nisso não venha diretamente da entrada do usuário sem ser cuidadosamente verificado!

    N=42
    a=( * )
    eval randf=( \"\${a[RANDOM%\${#a[@]}]\"\{1..$N\}\"}\" )

    Eu pessoalmente não gosto evale, portanto, esta resposta!

  • O mesmo usando um método mais direto (um loop):

    N=42
    a=( * )
    randf=()
    for((i=0;i<N;++i)); do
        randf+=( "${a[RANDOM%${#a[@]}]}" )
    done
  • Se você não deseja ter várias vezes o mesmo arquivo:

    N=42
    a=( * )
    randf=()
    for((i=0;i<N && ${#a[@]};++i)); do
        ((j=RANDOM%${#a[@]}))
        randf+=( "${a[j]}" )
        a=( "${a[@]:0:j}" "${a[@]:j+1}" )
    done

Nota . Essa é uma resposta tardia a uma postagem antiga, mas a resposta aceita está vinculada a uma página externa que mostra informações terríveis.prática, e a outra resposta não é muito melhor, pois também analisa a saída de ls. Um comentário à resposta aceita indica uma excelente resposta de Lhunath, que obviamente mostra boas práticas, mas não responde exatamente ao OP.

gniourf_gniourf
fonte
Primeiro e segundo produziram "má substituição"; não gostou da "{1..42}"parte deixando um rastro "1". Além disso, $RANDOMpossui apenas 15 bits e o método não funcionará com mais de 32767 arquivos para você escolher.
Yann Vernier
13
ls | shuf -n 10 # ten random files
silgon
fonte
1
Você não deve confiar na saída de ls. Isso não funcionará se, por exemplo, um nome de arquivo contiver novas linhas.
precisa saber é o seguinte
3
@ bfontaine você parece assombrado por novas linhas nos nomes de arquivos :). Eles são realmente tão comuns? Em outras palavras, existe alguma ferramenta que cria arquivos com novas linhas em seus nomes? Como usuário, é muito difícil criar esse nome de arquivo. Mesmo para arquivos provenientes da internet
Ciprian Tomoiagă
3
@CiprianTomoiaga Esse é um exemplo dos problemas que você pode ter. lsnão é garantido que você forneça nomes de arquivos "limpos", para que você não confie nele, ponto final. O fato de esses problemas serem raros ou incomuns não muda o problema; especialmente dado que existem melhores soluções para isso.
precisa saber é
lspode incluir diretórios e linhas em branco. Eu sugeriria algo como isso find . -type f | shuf -n10.
precisa saber é
9

Uma solução simples para selecionar 5arquivos aleatórios enquanto evita analisar ls . Também funciona com arquivos que contêm espaços, novas linhas e outros caracteres especiais:

shuf -ezn 5 * | xargs -0 -n1 echo

Substitua echopelo comando que você deseja executar para seus arquivos.

scai
fonte
1
bem, o pipe + não readtem os mesmos problemas que a análise ls? ou seja, ele lê linha por linha, por isso não funciona para arquivos com novas linhas em seu nome
Ciprian Tomoiagă
3
Você está certo. Minha solução anterior não funcionava para nomes de arquivos que contenham novas linhas e provavelmente quebra em outras pessoas com certos caracteres especiais também. Atualizei minha resposta para usar terminação nula em vez de novas linhas.
Scai
4

Se você possui o Python instalado (funciona com o Python 2 ou o Python 3):

Para selecionar um arquivo (ou linha de um comando arbitrário), use

ls -1 | python -c "import sys; import random; print(random.choice(sys.stdin.readlines()).rstrip())"

Para selecionar Narquivos / linhas, use (a nota Nestá no final do comando, substitua-a por um número)

ls -1 | python -c "import sys; import random; print(''.join(random.sample(sys.stdin.readlines(), int(sys.argv[1]))).rstrip())" N
Marca
fonte
Isso não funciona se o seu nome de arquivo contiver novas linhas.
precisa saber é o seguinte
4

Esta é uma resposta ainda mais tarde à resposta tardia de @ gniourf_gniourf, que acabei de votar porque é de longe a melhor resposta, duas vezes. (Uma vez para evitar evale outra para manipulação segura de nome de arquivo.)

Mas levei alguns minutos para desembaraçar o (s) recurso (s) não muito bem documentado (s) que esta resposta usa. Se suas habilidades no Bash forem sólidas o suficiente para você ver imediatamente como isso funciona, pule este comentário. Mas não o fiz e, depois de desembaraçar, acho que vale a pena explicar.

O recurso 1 é o globbing do arquivo do próprio shell. a=(*)cria uma matriz, $acujos membros são os arquivos no diretório atual. O Bash entende todas as estranhezas dos nomes de arquivos, para que a lista seja garantida correta, garantida como escapada, etc. Não é necessário se preocupar em analisar corretamente os nomes de arquivos de texto retornados por ls.

O recurso 2 é expansões de parâmetro Bash para matrizes , uma aninhada dentro de outra. Isso começa com ${#ARRAY[@]}, que se expande para o comprimento de $ARRAY.

Essa expansão é então usada para subscrever a matriz. A maneira padrão de encontrar um número aleatório entre 1 e N é pegar o valor do módulo número aleatório N. Queremos um número aleatório entre 0 e o comprimento da nossa matriz. Aqui está a abordagem, dividida em duas linhas por uma questão de clareza:

LENGTH=${#ARRAY[@]}
RANDOM=${a[RANDOM%$LENGTH]}

Mas esta solução faz isso em uma única linha, removendo a atribuição desnecessária de variáveis.

O recurso 3 é a expansão do suporte Bash , embora eu tenha que confessar que não o entendo completamente. Expansão Brace é usado, por exemplo, para gerar uma lista de 25 arquivos nomeados filename1.txt, filename2.txt, etc:echo "filename"{1..25}".txt" .

A expressão dentro do subshell acima,, "${a[RANDOM%${#a[@]}]"{1..42}"}"usa esse truque para produzir 42 expansões separadas. A expansão de colchete coloca um único dígito entre o ]e o }, que no começo eu pensei que estava assinando o array, mas, se fosse, seria precedido por dois pontos. (Ele também retornaria 42 itens consecutivos de um ponto aleatório na matriz, o que não é o mesmo que devolver 42 itens aleatórios da matriz.) Acho que está apenas fazendo o shell executar a expansão 42 vezes, retornando assim 42 itens aleatórios da matriz. (Mas se alguém puder explicar melhor, eu adoraria ouvir.)

A razão pela qual N precisa ser codificado (a 42) é que a expansão do braquete ocorre antes da expansão variável.

Finalmente, aqui está o recurso nº 4 , se você quiser fazer isso recursivamente para uma hierarquia de diretórios:

shopt -s globstar
a=( ** )

Isso ativa uma opção de shell que faz **corresponder recursivamente. Agora sua $amatriz contém todos os arquivos em toda a hierarquia.

Ken
fonte
2

Se você tiver mais arquivos em sua pasta, poderá usar o comando canalizado abaixo que encontrei no unix stackexchange .

find /some/dir/ -type f -print0 | xargs -0 shuf -e -n 8 -z | xargs -0 cp -vt /target/dir/

Aqui eu queria copiar os arquivos, mas se você quiser mover arquivos ou fazer outra coisa, basta alterar o último comando em que eu usei cp.

Bhaskar Chakradhar
fonte
1

Este é o único script que eu consigo jogar bem com o bash no MacOS. Combinei e editei trechos dos dois links a seguir:

Comando ls: como posso obter uma listagem de caminho completo recursiva, uma linha por arquivo?

http://www.linuxquestions.org/questions/linux-general-1/is-there-a-bash-command-for-picking-a-random-file-678687/

#!/bin/bash

# Reads a given directory and picks a random file.

# The directory you want to use. You could use "$1" instead if you
# wanted to parametrize it.
DIR="/path/to/"
# DIR="$1"

# Internal Field Separator set to newline, so file names with
# spaces do not break our script.
IFS='
'

if [[ -d "${DIR}" ]]
then
  # Runs ls on the given dir, and dumps the output into a matrix,
  # it uses the new lines character as a field delimiter, as explained above.
  #  file_matrix=($(ls -LR "${DIR}"))

  file_matrix=($(ls -R $DIR | awk '; /:$/&&f{s=$0;f=0}; /:$/&&!f{sub(/:$/,"");s=$0;f=1;next}; NF&&f{ print s"/"$0 }'))
  num_files=${#file_matrix[*]}

  # This is the command you want to run on a random file.
  # Change "ls -l" by anything you want, it's just an example.
  ls -l "${file_matrix[$((RANDOM%num_files))]}"
fi

exit 0
benmarbles
fonte
1

O MacOS não possui os comandos sort -R e shuf , então eu precisava de uma solução somente bash que randomize todos os arquivos sem duplicatas e não encontrou aqui. Esta solução é semelhante à solução nº 4 de gniourf_gniourf, mas, com sorte, adiciona melhores comentários.

O script deve ser fácil de modificar para parar após N amostras usando um contador com if, ou o loop for de gniourf_gniourf com N. $ RANDOM é limitado a ~ 32000 arquivos, mas isso deve ocorrer na maioria dos casos.

#!/bin/bash

array=(*)  # this is the array of files to shuffle
# echo ${array[@]}
for dummy in "${array[@]}"; do  # do loop length(array) times; once for each file
    length=${#array[@]}
    randomi=$(( $RANDOM % $length ))  # select a random index

    filename=${array[$randomi]}
    echo "Processing: '$filename'"  # do something with the file

    unset -v "array[$randomi]"  # set the element at index $randomi to NULL
    array=("${array[@]}")  # remove NULL elements introduced by unset; copy array
done
gato
fonte
0

Eu uso isso: ele usa arquivo temporário, mas vai profundamente em um diretório até encontrar um arquivo regular e devolvê-lo.

# find for a quasi-random file in a directory tree:

# directory to start search from:
ROOT="/";  

tmp=/tmp/mytempfile    
TARGET="$ROOT"
FILE=""; 
n=
r=
while [ -e "$TARGET" ]; do 
    TARGET="$(readlink -f "${TARGET}/$FILE")" ; 
    if [ -d "$TARGET" ]; then
      ls -1 "$TARGET" 2> /dev/null > $tmp || break;
      n=$(cat $tmp | wc -l); 
      if [ $n != 0 ]; then
        FILE=$(shuf -n 1 $tmp)
# or if you dont have/want to use shuf:
#       r=$(($RANDOM % $n)) ; 
#       FILE=$(tail -n +$(( $r + 1 ))  $tmp | head -n 1); 
      fi ; 
    else
      if [ -f "$TARGET"  ] ; then
        rm -f $tmp
        echo $TARGET
        break;
      else 
        # is not a regular file, restart:
        TARGET="$ROOT"
        FILE=""
      fi
    fi
done;
bzimage
fonte