Criando diretórios em um loop e movendo arquivos para esses diretórios

3

Considere um diretório com N arquivos.

Eu posso listar os arquivos classificados em ordem alfabética e armazenar a lista por

ls > list

Então eu quero criar n subdiretórios no mesmo diretório, o que pode ser feito por

mkdir direc{1..n}

Agora, quero mover o primeiro m ou dizer os primeiros 5 (1-5) arquivos de list para direc1 , os próximos 5 arquivos, ou seja, 6-10 para direc2 e assim por diante.

Esta pode ser uma tarefa trivial para vocês, mas não posso fazê-lo no momento. Por favor ajude.

    
por Polar.Ice 12.03.2015 / 22:01

4 respostas

3
list=(*)     # an array containing all the current file and subdir names
nd=5         # number of new directories to create
((nf = (${#list[@]} / nd) + 1))  # number of files to move to each dir
                                 # add 1 to deal with shell's integer arithmetic

# brace expansion doesn't work with variables due to order of expansions
echo mkdir $(printf "direc%d " $(seq $nd))

# move the files in chunks
# this is an exercise in arithmetic to get the right indices into the array
for (( i=1; i<=nd; i++ )); do
    echo mv "${list[@]:(i-1)*nf:nf}" "direc$i"
done

remova os dois comandos "echo" depois de ter testado isso.

Ou, se você quiser usar um número fixo de arquivos por diretório, isso é mais simples:

list=(*)
nf=10
for ((d=1, i=0; i < ${#list[@]}; d++, i+=nf)); do
    echo mkdir "direc$d"
    echo mv "${list[@]:i:nf}" "direc$d"
done
    
por 12.03.2015 / 22:22
2
#!/bin/sh
d=1                # index of the directory
f=0                # number of files already copied into direc$d
for x in *; do     # iterate over the files in the current directory
  # Create the target directory if this is the first file to be copied there
  if [ $f -eq 0 ]; then mkdir "direc$d"; fi
  # Move the file
  mv -- "$x" "direc$d"
  f=$((f+1))
  # If we've moved 5 files, go on to the next directory
  if [ $f -eq 5 ]; then
    f=0 d=$((d+1))
  fi
done

Referências úteis:

por 13.03.2015 / 01:22
2
d=0; set ./somedirname                         #init dir counter; name
for f in ./*                                   #glob current dir
do   [ -f "$f" ] && set "$f" "$@" &&           #if "$f" is file and...
     [ "$d" -lt "$((d+=$#>5))" ]  || continue  #d<(d+($#>5)) or continue
     mkdir "$6$d" && mv "$@$d"    || !  break  #mk tgtdir and mv 5 files or
     shift 5                                   #break with error
done

O comando acima aproveita a capacidade do array arg de shell para concatenar strings à sua cabeça e cauda. Por exemplo, se você escreveu uma função:

fn(){ printf '<%s>\n' "42$@"; }

... e chamou assim:

fn show me

... imprimiria:

<42show>
<me>

... porque você pode preceder ou anexar ao primeiro ou último elemento (respectivamente) na matriz arg simplesmente colocando suas aspas em volta da sua string pré / afixada.

A matriz arg também tem o dobro do direito, pois também serve como contador - o parâmetro $# shell sempre nos informará exatamente quantos elementos foram empilhados até o momento.

Mas ... Aqui está um passo a passo:

  1. %código%
    • O d=0; set ./somedirname var será incrementado em um para cada novo diretório criado. Aqui é inicializado para zero.
    • $d é o que você quiser. O prefixo ./somedirname é importante - não só enraiza todas as operações no diretório atual, mas também permite que você especifique qualquer nome que deseje (se você quiser ficar louco e usar novas linhas ou começar com hífens você pode seguramente - mas provavelmente não é aconselhável) . Porque o argname sempre será iniciado com ./ nenhum comando irá interpretá-lo erroneamente como uma opção em uma linha de comando.
  2. %código%
    • Isso inicia um loop sobre todos (se houver) das correspondências para ./ no diretório atual. Novamente, cada correspondência é prefixada com for f in ./* .
  3. %código%
    • verifica se a correspondência de cada iteração é definitivamente um arquivo regular (ou um link para um) e ...
  4. %código%
    • empilha as correspondências uma na frente da outra na matriz de shell. Desta forma, o * está sempre no final do array.
  5. %código%
    • adiciona 1 a ./ se houver mais de 5 elementos de matriz em [ -f "$f" ] , testando simultaneamente o resultado para um incremento.
  6. %código%
    • Se qualquer um dos comandos set "$f" "$@" ./somedirname [ "$d" -lt "$((d+=$#>5))" ] não retornar true, o loop continuará na próxima iteração e não tentará concluir o restante do loop. Isso é eficiente e seguro.
  7. %código%
    • Porque a cláusula $d garante que só podemos chegar a este ponto se "$@" our || continue estiver agora em [ -f "$f" ] e o valor de set ... tiver sido incrementado em apenas um. Assim, para o primeiro grupo de 5 arquivos correspondidos e movidos, isso cria um diretório chamado [ "$d" -lt... e o quinto mkdir "$6$d" e assim por diante. Importante, este comando falha se qualquer nome de caminho com o nome do caminho de destino já existir. Em outras palavras, este comando é somente bem sucedido se definitivamente não houver nenhum diretório com o mesmo nome já existente.
  8. continue

    • Isso expande a matriz enquanto afixa o valor de $# > 5 à cauda do último elemento - que é o nome do diretório de destino. Então se expande como:

    ./somedirname

    • ... o que é exatamente o que você quer que aconteça.
  9. $6

    • Se um dos dois comandos anteriores não for concluído com êxito por algum motivo, o $d loop ./somedirname1 s. O ./somedirname5 envia o inverso booleano do retorno de mv "$@$d" - que é tipicamente zero - então $d retorna 1. Dessa forma, o loop retornará falso se algum erro ocorrer em qualquer um dos comandos anteriores. Isso é importante - mv ./file5 ./file4 ./file3 ./file2 ./file1 ./somedirname1 loops - ao contrário de || ! break loops - não implica em testes, apenas em iteração. Sem testar explicitamente o retorno desses dois comandos, o shell não necessariamente parará no erro - e for provavelmente acabará com o shell pai. Em vez disso, isso garante um retorno significativo e que o loop não continuará a iterar se algo der errado.

    • Em uma rápida olhada, parece que esta é a única resposta aqui que irá parar, por exemplo, se break não retornar verdadeiro - todos os outros continuarão circulando (e provavelmente repetirão o erro, ou, pior ainda, mover arquivos no diretório atual para um diretório existente e possivelmente sobre outros arquivos com o mesmo nome lá) . Trabalhando com nomes de arquivos arbitrários em loops, você deve sempre testar a existência do arquivo de origem e para a existência do destino.

  10. %código%
    • Esse ! s afasta os primeiros 5 elementos da matriz arg do shell - o que coloca break de volta em break e redefine o estado da matriz para a próxima iteração.
por 12.03.2015 / 22:29
1

Com este programa awk, você pode criar os comandos do shell e, em caso de dúvida, inspecionar antecipadamente se estão corretos ...

awk -v n=5 '{ printf "mv \"%s\" %s\n", $0, "direc" int((NR-1)/n)+1 }' list

Se você está bem com o pipe de saída todo o comando em sh . Além disso, se você quiser evitar o arquivo extra 'list', você pode criá-lo imediatamente; todo o programa seria então ...

ls  |  awk -v n=5 '{ printf "mv \"%s\" %s\n", $0, "direc" int((NR-1)/n)+1 }'  |  sh

Você pode definir outros valores além de 5 se alterar a configuração n = 5.

Se você também quiser criar diretórios de destino rapidamente, aqui está uma variante ...

ls  |  awk -v n=5 '
         NR%n==1 { ++c ; dir="direc"c ; print "mkdir "dir }
         { printf "mv \"%s\" %s\n", $0, dir }
'  |  sh
    
por 12.03.2015 / 22:29