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?
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.
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:
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!
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.festançaprá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.
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.
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.
#!/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
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 arraydone
ls | shuf -n 5
Fonte: Unix StackexchangeRespostas:
Aqui está um script que usa a opção aleatória do GNU sort:
fonte
"$file"
, não mostrado, seria sensível aos espaços.ls
?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:Ajuste o
-n, --head-count=COUNT
valor para retornar o número de linhas desejadas. Por exemplo, para retornar 5 nomes de arquivos aleatórios, você usaria:fonte
N
arquivos aleatórios, portanto, usar1
é um pouco enganador.find dirname -type f -print0 | shuf -zn1
Aqui estão algumas possibilidades que não analisam a saída
ls
e 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 matrizrandf
com 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
N
precisa ser conhecido com antecedência. Aqui eu escolhi N = 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 queN
isso não venha diretamente da entrada do usuário sem ser cuidadosamente verificado!Eu pessoalmente não gosto
eval
e, portanto, esta resposta!O mesmo usando um método mais direto (um loop):
Se você não deseja ter várias vezes o mesmo arquivo:
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.festançaprá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.fonte
"{1..42}"
parte deixando um rastro"1"
. Além disso,$RANDOM
possui apenas 15 bits e o método não funcionará com mais de 32767 arquivos para você escolher.fonte
ls
. Isso não funcionará se, por exemplo, um nome de arquivo contiver novas linhas.ls
nã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.ls
pode incluir diretórios e linhas em branco. Eu sugeriria algo como issofind . -type f | shuf -n10
.Uma solução simples para selecionar
5
arquivos aleatórios enquanto evita analisar ls . Também funciona com arquivos que contêm espaços, novas linhas e outros caracteres especiais:Substitua
echo
pelo comando que você deseja executar para seus arquivos.fonte
read
tem os mesmos problemas que a análisels
? ou seja, ele lê linha por linha, por isso não funciona para arquivos com novas linhas em seu nomeSe 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
Para selecionar
N
arquivos / linhas, use (a notaN
está no final do comando, substitua-a por um número)fonte
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
eval
e 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,$a
cujos 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 porls
.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:
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:
Isso ativa uma opção de shell que faz
**
corresponder recursivamente. Agora sua$a
matriz contém todos os arquivos em toda a hierarquia.fonte
Se você tiver mais arquivos em sua pasta, poderá usar o comando canalizado abaixo que encontrei no unix stackexchange .
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
.fonte
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/
fonte
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.
fonte
Eu uso isso: ele usa arquivo temporário, mas vai profundamente em um diretório até encontrar um arquivo regular e devolvê-lo.
fonte
Que tal uma solução Perl levemente adulterada pelo Sr. Kang aqui:
Como posso embaralhar as linhas de um arquivo de texto na linha de comando do Unix ou em um script de shell?
fonte