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


144

Eu tenho um diretório com cerca de 2000 arquivos. Como posso selecionar uma amostra aleatória de Narquivos usando um script bash ou uma lista de comandos canalizados?


1
Também é uma boa resposta no Unix e Linux: unix.stackexchange.com/a/38344/24170 #
Nikana Reklawyks


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

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

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.


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

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.


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

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.


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.


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

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

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;

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.