Unix: como tar apenas N primeiros arquivos de cada pasta?

7

Eu tenho uma pasta contendo 2Gb de imagens, com subpastas com vários níveis de profundidade.

Gostaria de arquivar apenas N arquivos de cada (sub) pasta em um arquivo tar. Tentei usar find , em seguida, tail then tar , mas não consegui fazer com que funcionasse. Aqui está o que eu tentei (assumindo N = 10 ):

find . | tail -n 10 | tar -czvf backup.tar.gz

… o que gera este erro:

Cannot stat: File name too long

O que há de errado aqui? pensando nisso - mesmo que funcione eu acho que ele irá tar apenas os 10 primeiros arquivos de todas as pastas, não os 10 arquivos de cada pasta.

Como posso obter N arquivos de cada pasta? (Nenhuma ordem de arquivo necessária)

    
por Sam 17.08.2015 / 19:17

7 respostas

4

Se o seu pax suportar a opção -0 , com zsh :

print -rN dir/**/*(D/e:'reply=($REPLY/*(ND^/[1,10]))':) |
  pax -w0 | xz > file.tar.xz

Inclui os 10 primeiros arquivos que não são de diretório de cada diretório da lista, classificados por nome de arquivo. Você pode escolher uma ordem de classificação diferente, adicionando o qualificador om glob (ordem por tempo de modificação, Om para reverter a ordem), oL (ordem por tamanho), non (classificar por nome, mas numericamente). ..

Se você não tem o comando padrão pax , ou ele não suporta -0 , mas você tem o comando GNU tar , você pode fazer:

print -rN -- dir/**/*(D/e:'reply=($REPLY/*(ND^/[1,10]))':) |
  tar --null -T - -cjf file.tar.xz

Se você não pode usar zsh , mas tem acesso a bash (o shell do projeto GNU), você poderia fazer:

find dir -type d -exec bash -O nullglob -O dotglob -c '
  for dir do
    set -- "$dir/*"; n=0
    for file do
      if [ ! -d "$file" ] || [ -L "$file" ]; then
        printf "%s
print -rN dir/**/*(D/e:'reply=($REPLY/*(ND^/[1,10]))':) |
  pax -w0 | xz > file.tar.xz
" "$file" (( n++ < 10 )) || break fi done done' bash {} + | pax -0w | xz > file.tar.xz

Isso seria significativamente menos eficiente.

    
por 18.08.2015 / 11:30
2

Suponha que o seu diretório mestre seja /tmp/dir do qual você deseja arquivar somente N (por exemplo, N = 10) arquivos de cada (sub) pasta sob ele para um arquivo backup.tar.gz .

Exemplo tree para /tmp/dir :

dir/                                                                                                                                                                                                           
├── one
│   ├── one10.txt
│   ├── one11.txt
│   ├── one1.txt
│   ├── one2.txt
│   ├── one3.txt
│   ├── one4.txt
│   ├── one5.txt
│   ├── one6.txt
│   ├── one7.txt
│   ├── one8.txt
│   ├── one9.txt
│   └── one_deep
│       ├── one_deep1
│       ├── one_deep10
│       ├── one_deep11
│       ├── one_deep2
│       ├── one_deep3
│       ├── one_deep4
│       ├── one_deep5
│       ├── one_deep6
│       ├── one_deep7
│       ├── one_deep8
│       └── one_deep9
├── three
│   ├── three10.txt
│   ├── three11.txt
│   ├── three1.txt
│   ├── three2.txt
│   ├── three3.txt
│   ├── three4.txt
│   ├── three5.txt
│   ├── three6.txt
│   ├── three7.txt
│   ├── three8.txt
│   ├── three9.txt
│   └── three_deep
│       ├── three_deep1
│       ├── three_deep10
│       ├── three_deep11
│       ├── three_deep2
│       ├── three_deep3
│       ├── three_deep4
│       ├── three_deep5
│       ├── three_deep6
│       ├── three_deep7
│       ├── three_deep8
│       └── three_deep9

Código:

cd /tmp; for i in 'find dir/* -type d'; do find $i -maxdepth 1 -type f | tail -n 10 | xargs -I file tar -rf backup.tar file; done; gzip backup.tar

Isso criará um backup.tar.gz com 10 arquivos de cada subpasta abaixo de /tmp/dir .

    
por 17.08.2015 / 20:44
2

Como a saída de find é simples, você não sabe realmente quais arquivos pertencem aos mesmos diretórios sem olhar para os caminhos. A alternativa é usar vários find s (um por pasta), sem precisar examinar os caminhos. Isso foi o que eu fiz. Para tarar até um máximo de 10 arquivos de cada subpasta, use algo assim:

for dir in $(find . -type d); do
  find "$dir" -maxdepth 1 -type f -printf "\"%p\"\n" | tail -10
done | xargs tar cvfz backup.tar.gz

Isso recursivamente localiza todos os diretórios na pasta atual. Para cada diretório, ele encontra até 10 arquivos em exatamente nessa pasta ( -maxdepth 1 ). Quando o loop inteiro for concluído, o comando tar será executado em todos os arquivos que foram gerados pelo loop. Também contabilizei nomes de pastas e diretórios com espaços, citando $dir e tendo find imprimindo cada nome de arquivo entre aspas, usando a opção -printf .

    
por 17.08.2015 / 19:59
1
for d in ./*/
do
    cd "$d"
    tar -rvf ../backup.tar $(ls | tail -10)
    cd ..
done
gzip backup.tar

outra variante

find * -prune -type d -exec bash -c 'printf "%s\n" $0/* | tail -10' {} \; |
tar czvf backup.tar.gz -T -
    
por 17.08.2015 / 21:28
0

Use um hash no nome do diretório e apenas emita o nome do arquivo se a contagem do valor de hash estiver abaixo do limite. Por exemplo,

find . -depth -type f \
| perl -MFile::Spec -nle '(undef,$d,$f)=File::Spec->splitpath($_); print if $seen{$d}++ < 3' \
| tar ...
    
por 17.08.2015 / 19:47
0

A maneira mais fácil (ou mais fácil de entender) é usar xargs com a opção -N max-args .

Tenha em mente que sua entrada sempre precisa ser algo, o que não requer uma linha de comando, então echo *.* funcionará como entrada, onde ls *.* não (por muito tempo ls linha de comando)

find deve estar bem, já que seu argumento é apenas o caminho, não uma lista de arquivos.

    
por 17.08.2015 / 23:16
0

O OP fez esta no Stackoverflow também . Aqui está a resposta que eu ofereci lá.

A seleção e a ordem dos arquivos nessa resposta são determinadas pela ordem de find , portanto, "primeiro" não está bem definido aqui. Isso também pode depender do GNU Awk 4.1.0.

find . -type f |
awk -v N=10 -F / 'match($0, /.*\//, m) && a[m[0]]++ < N' |
xargs -r -d '\n' tar -rvf /tmp/backup.tar

gzip /tmp/backup.tar

Comentários:

  1. use find . -type f para garantir que os arquivos tenham um prefixo de nome de diretório principal, para que a próxima etapa possa funcionar
  2. o comando awk rastreia esses nomes de diretório principais e emite nomes de caminho completos até que N (10, aqui) arquivos com o mesmo diretório principal tenham sido emitidos (talvez mais simples awk use - dividindo informações padrão e programa - ser mais portátil)
  3. use xargs para invocar tar - estamos coletando nomes de arquivos regulares e eles precisam ser argumentos para esse comando de arquivamento
  4. xargs pode invocar tar mais de uma vez, então vamos anexar (opção -r) a um arquivo simples, depois compactá-lo depois que tudo estiver escrito

Além disso, talvez você não queira gravar um arquivo de backup no diretório atual, já que está digitalizando isso - é por isso que essa sugestão é gravada em / tmp.

    
por 20.08.2015 / 21:25

Tags