Escrevendo um caractere N vezes usando o comando printf

12

Encontrei o seguinte comando para repetir um caractere no Linux:

printf 'H%.0s' {1..5000} > H.txt

Eu quero, por exemplo, Hrepetir os 5000tempos. O que %.0ssignifica aqui?

Estrela
fonte
Com tcshou zsh, repeat 5000 printf Hé mais fácil de entender. Com perl: print "H" x 5000(note que esse {1..5000}é um operador zsh inspirado por perl's 1..5000um e depois copiado por ksh93 e bash)
Stéphane Chazelas
sim ele funciona, mas usa um monte de recursos para ampliar repetição, siga as sugestões por Stéphane Chazelas
Skaperen
1
Eu faria isso de comandoyes H|head -5000|tr -d '\012'
Skaperen
dd if=/dev/zero bs=5000 count=1 | tr '\0' H
Kojiro
@Skaepren:yes H| head -n 2500| tr \\n H
mikeserv

Respostas:

20

Esse comando depende do shell gerar 5000 argumentos e passá-los aos printfquais os ignora. Embora possa parecer bastante rápido - e seja relativo a algumas coisas - o shell ainda deve gerar todas essas strings como args (e delimitá-las) e assim por diante.

Além do fato de que os Hs gerados não podem ser impressos até que o shell itere primeiro para 5000, esse comando também custa na memória tudo o que é necessário para armazenar e delimitar os argumentos de sequência numérica para printf somar os Hs. Da mesma maneira que você pode:

printf %05000s|tr \  H

... que gera uma sequência de 5.000 espaços - que, pelo menos, geralmente são apenas um byte e não custam nada para delimitar porque não são delimitados. Alguns testes indicam que, mesmo com apenas 5000 bytes, o custo do garfo e do tubo necessário trvale a pena mesmo nesse caso, e quase sempre ocorre quando os números aumentam.

Eu corri...

time bash -c 'printf H%.0s {1..5000}' >/dev/null

...e...

time bash -c 'printf %05000s|tr \  H' >/dev/null

Cada uma cerca de 5 vezes por peça (nada de científico aqui - apenas anedótico) e a versão de expansão da cinta trtiveram em média um pouco mais de 0,02 segundos no tempo total de processamento, mas a versão chegou em média 0,012 segundos no total - e a trversão foi melhorada toda vez. Não posso dizer que estou surpreso - {brace expansion}é um recurso de taquigrafia interativo útil do shell, mas geralmente é uma coisa muito esbanjadora para se fazer em qualquer tipo de script. A forma comum:

for i in {[num]..[num]}; do ...

... quando você pensa sobre isso, são realmente dois for loops - o primeiro é interno e está implícito, pois o shell deve fazer um loop de alguma maneira para gerar esses iteradores antes de salvá-los e iterá-los novamente para o seu forloop. Geralmente, essas coisas são feitas melhor como:

iterator=$start
until [ "$((iterator+=interval))" -gt "$end" ]; do ...

... porque você armazena apenas muito poucos valores e os substitui à medida que avança, além de fazer a iteração enquanto gera os iteráveis.

De qualquer forma, como o preenchimento de espaço mencionado anteriormente, você também pode usar printfpara zeropar um número arbitrário de dígitos, é claro, como:

printf %05000d

Faço as duas coisas sem argumentos, porque para cada argumento especificado na printfstring de formato de um argumento, quando não é encontrado, a string nula é usada - que é interpretada como zero para um argumento de dígito ou vazia para uma string.

Esse é o outro lado (e - na minha opinião - mais eficiente) da moeda quando comparado com o comando da pergunta - embora seja possível não obter nada de algo como você faz ao printf %.0estender as seqüências de caracteres para cada argumento, também é é possível obter algo do nada.

Ainda mais rápido para grandes quantidades de bytes gerados, você pode usar ddcomo:

printf \\0| dd bs=64k conv=sync 

... e ddo seek=[num]argumento de arquivos regulares pode ser usado para maior vantagem. Você pode obter novas linhas de 64k em vez de nulas se adicionar as opções ,unblock cbs=1acima e a partir daí poderá injetar seqüências arbitrárias por linha com pastee /dev/null- mas nesse caso, se estiver disponível, você também poderá usar:

yes 'output string forever'

Aqui estão mais alguns ddexemplos de qualquer maneira:

dd bs=5000 seek=1 if=/dev/null of=./H.txt

... que cria (ou trunca) um \0NULarquivo preenchido no diretório atual chamado H.txt com tamanho de 5000 bytes. ddprocura direto para o offset e preenche NUL por trás dele.

<&1 dd bs=5000 conv=sync,noerror count=1 | tr \\0 H >./H.txt

... que cria um arquivo com o mesmo nome e tamanho, mas preenchido com caracteres H / H. Ele aproveita ddo comportamento spec'd de escrever pelo menos um nulo-quarteirão inteiro em caso de um erro de leitura quando noerrore syncconversões são especificados (e - sem count=- provavelmente continuar mais tempo do que você poderia querer) , e intencionalmente redirecionamentos um descritor de arquivo writeonly no ddstdin.

mikeserv
fonte
8

Os %.0smeios para converter o argumento como uma string , com uma precisão de zero. De acordo com man 3 printf, o valor de precisão nesse caso fornece

   [ ... ] the  maximum  number  of characters to be printed from a
   string for s and S conversions.

portanto, quando a precisão é zero, o argumento da string não é impresso. No entanto, o H(que faz parte do especificador de formato) é impresso quantas vezes houver argumentos, pois de acordo com a printfseçãoman bash

The format is reused as necessary to consume all  of  the  argu
ments.  If the format requires more arguments than are supplied,
the extra format specifications behave as if  a  zero  value  or
null  string,  as  appropriate,  had  been supplied. 
chave de aço
fonte
7

Nesse caso, %.0ssempre imprime uma instância do (s) caractere (s) que o precede, H neste caso. Quando você usa {1..5000}, o shell o expande e se torna:

printf 'H%.0s' 1 2 3 4 ... 5000 > H.txt

ou seja, o comando printf agora tem 5000 argumentos e, para cada argumento, você receberá um H. Estes não precisam ser seqüenciais ou numéricos:

printf 'H%.0s' a bc fg 12 34

imprime HHHHH- ou seja, o número de argumentos, 5 neste caso.

Observe que as elipses no 1º exemplo acima não são inseridas literalmente, elas estão lá para indicar uma sequência ou intervalo.

KM.
fonte