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, H repetir 5000 times. O que significa %.0s aqui?

    
por star 06.03.2015 / 21:22

3 respostas

20

Esse comando depende do shell que gera 5000 argumentos e passa-os para printf , que os ignora. Embora possa parecer bem rápido - e é 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 seja iterado em 5.000, esse comando também custa na memória tudo o que é necessário para armazenar e delimitar os argumentos da string numérica para printf plus o Hs. Assim como simplesmente você pode fazer:

printf %05000s|tr \  H

... que gera uma cadeia de 5000 espaços - que, pelo menos, são geralmente apenas um único byte e não custam nada para serem delimitados porque não são delimitados. Alguns testes indicam que, mesmo para apenas 5000 bytes, o custo do fork e do pipe necessários para tr vale a pena, mesmo neste caso, e quase sempre é 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 cerca de 5 vezes uma parte (nada de científico aqui - apenas anedótico) e a versão de expansão da chave demorou um pouco mais de .02 segundos no tempo total de processamento, mas a versão tr chegou cerca de 0,012 segundo total em média - e a versão tr venceu todas as vezes. Eu não posso dizer que estou surpreso - {brace expansion} é um recurso de abreviação de shell interativo útil, mas geralmente é uma coisa bastante esbanjadora para fazer onde qualquer tipo de script está em causa. A forma comum:

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

... quando você pensa sobre isso, é realmente dois for loops - o primeiro é interno e implícito em que o shell deve fazer um loop de alguma forma para gerar esses iteradores antes de salvá-los todos e iterando-os novamente para o loop for . Geralmente, essas coisas são mais bem feitas como:

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

... porque você armazena apenas alguns poucos valores e sobrescreve-os conforme 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 printf para zerar um número arbitrário de dígitos, é claro, como:

printf %05000d

Eu faço ambos sem argumentos porque para cada argumento especificado na string de formato printf quando um argumento não é encontrado, a string nula é usada - que é interpretada como zero para um argumento numérico ou uma string vazia para uma string .

Este é o outro lado (e - na minha opinião - mais eficiente) da moeda quando comparado com o comando na questão - enquanto é possível não obter nada de algo como você faz quando você printf %.0 length strings para cada argumento, então também é possível obter algo do nada.

Mais rápido ainda para grandes quantidades de bytes gerados, você pode usar dd como:

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

... e w / arquivos regulares O argumento dd seek=[num] pode ser usado para maior vantagem. Você pode obter 64k newlines em vez de nulls se adicionar ,unblock cbs=1 ao acima e de lá puder injetar strings arbitrárias por linha com paste e /dev/null - mas, nesse caso, se estiver disponível para você, você pode bem usar:

yes 'output string forever'

Aqui estão mais alguns exemplos de dd :

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

... que cria (ou trunca) um arquivo ddNUL preenchido no diretório atual chamado H.txt de tamanho 5000 bytes. dd procura diretamente no deslocamento e NUL-preenche todos atrás dele.

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

... que cria um arquivo de mesmo nome e tamanho, mas preenchido com caracteres H / H. Ele tira proveito do comportamento spec'd de noerror de gravar pelo menos um bloco nulo completo no caso de um erro de leitura quando sync e count= conversões são especificados (e - sem dd - provavelmente duraria mais do que você poderia querer) , e redireciona intencionalmente um descritor de arquivo writeonly na stdin de %code% .

    
por 06.03.2015 / 22:49
8

O %.0s significa converter o argumento como uma string , com uma precisão de zero. De acordo com man 3 printf , o valor de precisão em tal 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 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 seção printf de man 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. 
    
por 06.03.2015 / 21:49
7

Nesse caso, %.0s sempre imprime uma ocorrência de caractere precedente, H neste caso. Quando você usa {1..5000}, o shell expande e se torna:

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

, ou seja, o comando printf agora possui 5000 argumentos e, para cada argumento, você obterá um H. Esses não precisam ser sequenciais ou numéricos:

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

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

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

    
por 06.03.2015 / 21:48

Tags