Copie uma porcentagem específica de cada arquivo em um diretório para um novo arquivo

4

Por exemplo, temos N arquivos (arquivo1, arquivo2, arquivo3 ...)

Precisamos primeiro de 20% deles, o diretório de resultados deve ser como (file1_20, file2_20, file3_20 ...).

Eu estava pensando em usar wc para obter as linhas do arquivo, depois 0,2

Em seguida, use head para obter 20% e, em seguida, redirecione para um novo arquivo, mas não sei como automatizá-lo.

    
por Wilbeibi 06.12.2014 / 04:15

3 respostas

6

Então, criando um único exemplo para trabalhar:

root@crunchbang-ibm3:~# echo {0..100} > file1        
root@crunchbang-ibm3:~# cat file1
    0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100

Podemos pegar o tamanho do arquivo em bytes com stat :

root@crunchbang-ibm3:~# stat --printf %s "file1"
294

E, em seguida, usando bc , podemos multiplicar o tamanho por .2

root@crunchbang-ibm3:~# echo "294*.2" | bc
58.8

No entanto, obtemos um float, então vamos convertê-lo em um inteiro para head ( dd pode funcionar aqui também):

root@crunchbang-ibm3:~# printf %.0f "58.8" 
59

E finalmente os primeiros vinte por cento (mais ou menos um byte) do arquivo1:

root@crunchbang-ibm3:~# head -c "59" "file1" 
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22

Juntando tudo, poderíamos fazer algo assim

mkdir -p a_new_directory
for f in file*; do
    file_size=$(stat --printf %s "$f")
    percent_size_as_float=$(echo "$file_size*.2" | bc)
    float_to_int=$(printf %.0f "$percent_size_as_float")
    grab_twenty=$(head -c "$float_to_int" "$f")
    new_fn=$(printf "%s_20" "$f") # new name file1_20
    printf "$grab_twenty" > a_new_directory/$new_fn
done

onde f é um espaço reservado para todos os itens encontrados no diretório em que o loop for executado e corresponde a file*

quando feito:

root@crunchbang-ibm3:~# cat a_new_directory/file1_20
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 

atualizar (para pegar 20% das linhas):

Para pegar os primeiros 20% das linhas, podemos substituir stat --printf %s "$f" por:

wc -l < "$f"

Como estamos usando printf e bc , podemos efetivamente fazer um arredondamento a partir de .5 , no entanto, se um arquivo tiver apenas 1 ou 2 linhas, será perdido. Então, gostaríamos de não apenas arredondar, mas padrão para, pelo menos, pegar uma linha.

    
por 06.12.2014 / 05:02
5

Dang. Eu escrevi toda essa grande resposta com um método elaborado de analisar um arquivo tar - foi legal. Mas cheguei ao fim e percebi que nada disso era necessário. Tudo que você precisa é sed e um pouco de matemática:

set ./file[1-5];i=1 n=;eval "${n:=
}       sed -n  \"$(grep -c '.\|' "$@"|
        sed 's|\(.*\):\(.*\)|\
        $i,$(((/5)+(i+=)-))w |
        ')\" <<!$n"'$(cat "$@")'"$n!$n"

grep -c conta linhas em quaisquer arquivos que você tenha globbed - eu globbed file[1-5] - e entrega a contagem para sed , que então - com uma pequena ajuda do shell - grava seu próprio script. cat fornece a entrada via here-document. Isto é porque eu sou duvidoso sobre o que pode acontecer se sed abrir e começar a escrever para um dos arquivos cat está tentando ler para ele - também eu suspeito que seria um pouco melhor no manuseio buffers do que um pipe seria dependendo do tamanho - mas eu não sou muito claro sobre essa parte.

Assim, todos os arquivos são lidos em um único fluxo e w processa a saída de acordo. É necessária uma pequena configuração para incrementar os números dos arquivos corretamente - daí grep e eval - nada terrível. Aqui está uma saída de set -x para mostrar o que está fazendo:

+ set ./file1 ./file2 ./file3 ./file4 ./file5
+ i=1 n=
+ + grep -c .\| ./file1 ./file2 ./file3 ./file4 ./file5
        sed s|\(.*\):\(.*\)|\
        $i,$(((/5)+(i+=)-))w |

+ eval 
       sed -n  "
        $i,$(((18400/5)+(i+=18400)-18400))w ./file1

        $i,$(((18411/5)+(i+=18411)-18411))w ./file2

        $i,$(((18415/5)+(i+=18415)-18415))w ./file3

        $i,$(((18418/5)+(i+=18418)-18418))w ./file4

        $i,$(((18421/5)+(i+=18421)-18421))w ./file5" <<!
$(cat "$@")
!

+ cat ./file1 ./file2 ./file3 ./file4 ./file5
+ sed -n 
        1,3681w ./file1

        18401,22083w ./file2

        36812,40495w ./file3

        55227,58910w ./file4

        73645,77329w ./file5

Como você pode ver, as linhas são endereçadas com base na posição de cada arquivo no fluxo e são w ritten conforme são lidas em seus respectivos nomes de arquivos. Importante, porém, isso não faz nenhuma tentativa de manipular nenhum caractere não-portátil em um nome de caminho - em particular, novas linhas em nomes de caminho não são iniciais neste caso, pois o comando sed w rite delimita argumentos de nome de arquivo em novas linhas. A situação é facilmente contornada, se necessário, com ln , se você precisar.

Também devo mencionar que há um limite para o número de w rite descritores de arquivos sed podem suportar em um único script. A especificação diz :

[sed is required] to support at least ten distinct wfiles, matching historical practice on many implementations. Implementations are encouraged to support more, but conforming applications should not exceed this limit.

Portanto, o comando como descrito acima deve ser portátil para qualquer sistema POSIX para até 10 arquivos de leitura / gravação simultâneos. Se esse tipo de coisa fosse incorporado em um script ou aplicativo publicado em que mais poderia ser desejado, valeria a pena executar algumas verificações antes de processar dados reais em /tmp . Como:

: & set '"" "" "" "" "" "" "" "" "" "" ';n='
' f=/tmp/$$$!'_$((i+=1))' MAXw=[num]
while eval "set '$1$1' $1;exec <<!$n\$(((i=0)+\$#))$n!$n 
      i=\$(sed \"$(IFS=\ ;printf "\nw $f%.0s" $1)\")"
      [ "$(($#==i?(_i=i-1):(MAXw=_i)))" -lt "$MAXw" ]
do :;done; rm "/tmp/$$$!"*; unset _i i f n

... o qual deve razoavelmente portar a capacidade de sed nessa área. O GNU sed parou em 4093 arquivos w abertos simultaneamente para mim em cerca de um segundo, mas isso é provavelmente o máximo do meu sistema, e pode ser afetado com ulimit também. Quando acabou - porque a verificação dobra o valor de $i para cada tentativa - $_i foi deixado em 2560 e $i em 5120. Eu padrão para definir $MAXw para o mais seguro $_i acima em loop close - principalmente porque não tenho certeza se todos os sed s irão definir corretamente o seu retorno se eles não puderem abrir um arquivo w - mas o leitor pode fazer com o que eles quiserem.

Observe que o valor [num] inicial para $MAXw deve ser um número real - seja qual for o número máximo desejado de arquivos w - e não literalmente [num] .

Sobre o documento aqui novamente - eu o considero - ou algo parecido - uma boa ideia neste caso. sed deve manter seus descritores de gravação enquanto lê e então o que poderia fazer com nomes idênticos de entrada / saída que eu não conheço - mas eu não acho que seja uma chance que vale a pena quando as alternativas estão prontamente disponíveis para nós. / p>

Meus arquivos de teste foram gerados como:

for n in 1 2 3 4 5
do : & seq -s "$(printf "%015s--$n--%015s\n\t")" "$!" >"file$n"
done

... que obtém números pseudo-aleatórios razoavelmente sequenciais do kernel em PIDs de processo abandonados. O conteúdo do arquivo foi propositadamente projetado para indicar uma incompatibilidade na divisão. Veja como é um conjunto de amostras antes e depois:

Antes:

for f in file[1-5]; do
nl -ba "$f" | sed -n '$p;$=;1,3p
'; done

     1  1               --1--             
     2          2               --1--     
     3          3               --1--     
  3681          3681               --1--  
3681
     1  1               --2--             
     2          2               --2--     
     3          3               --2--     
  3683          3683               --2--  
3683
     1  1               --3--             
     2          2               --3--     
     3          3               --3--     
  3684          3684               --3--  
3684
     1  1               --4--             
     2          2               --4--     
     3          3               --4--     
  3684          3684               --4--  
3684
     1  1               --5--             
     2          2               --5--     
     3          3               --5--     
  3685          3685               --5--  
3685

Se a formatação parecer um pouco funky, provavelmente é porque seq não insere a string -s eparator antes da primeira linha de saída. O importante é que sed , seq e nl pareçam concordar com os números de linha. Enfim ...

Depois:       ...

  sed -n 
  1,737w ./file1

  3682,4418w ./file2

  7365,8101w ./file3

  11049,11785w ./file4

  14733,15470w ./file5
  ...
     1  1               --1--           
     2          2               --1--   
     3          3               --1--   
   737          737               --1-- 
737
     1  1               --2--           
     2          2               --2--   
     3          3               --2--   
   737          737               --2-- 
737
     1  1               --3--           
     2          2               --3--   
     3          3               --3--   
   737          737               --3-- 
737
     1  1               --4--           
     2          2               --4--   
     3          3               --4--   
   737          737               --4-- 
737
     1  1               --5--           
     2          2               --5--   
     3          3               --5--   
   738          738               --5-- 
738

E isso é simples, eficiente e transmitido.

    
por 07.12.2014 / 07:02
4

Usando as ferramentas que você mencionou + find :
obter porcentagem de linhas ou bytes 1 com head -n perc file ou head -c perc file ,
onde perc é dado por (( count / 5 )) ,
onde count é dado por wc -l < file ou wc -c < file ,
finalmente, escreva a saída para file_20 correspondente.

Observação: o operador / é arredondado para o número inteiro mais próximo, portanto, qualquer file* com linhas / bytes count < 5 (daí perc = 0 ) produzirá um arquivo file*_20 vazio.

obtenha as primeiras 20% de linhas:

mkdir some_dir_name
find . -maxdepth 1 -iname 'file*' -exec sh -c 'head -n $(( $(wc -l < "$0") / 5 )) "$0" > some_dir_name/"$0"_20' {} \;

obtenha primeiro 20% - bytes:

mkdir some_dir_name
find . -maxdepth 1 -iname 'file*' -exec sh -c 'head -c $(( $(wc -c < "$0") / 5 )) "$0" > some_dir_name/"$0"_20' {} \;

1
Note-se que, dependendo do layout do texto, os dois métodos podem produzir resultados significativamente diferentes, e. para uma amostra de texto de 10 linhas:

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua.


Abstract

Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi
ut aliquip ex ea commodo consequat. 

Duis aute irure dolor in reprehenderit in voluptate velit esse cillum...

primeiros 20% do número total de linhas = primeiras 2 linhas:

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua.

primeiros 20% do número total de bytes = primeira linha (truncada):

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
    
por 06.12.2014 / 06:25