Primeiro, aqui estão algumas comparações cronometradas de algumas outras soluções oferecidas:
time \
bash -c '
for i in {1..1000}
do printf "%0${i}s\n"
done| sed "y/ /*/"
' >/dev/null
real 0m0.017s
user 0m0.023s
sys 0m0.000s
Isso não é ruim, embora eu tenha adicionado uma pequena otimização usando uma expressão de tradução y/ /*/
em vez de uma instrução de substituição de expressão regular s/ /*/g
.
time \
bash -c '
for i in {1..1000}
do a=$(printf "%0${i}s")
echo "${a// /*}"
done
' >/dev/null
real 0m1.337s
user 0m0.723s
sys 0m0.187s
Uau. Isso é terrível.
Aqui está uma que eu sugeriria que você usasse se estivesse realmente empenhado em uma solução somente de shell. A vantagem disso em relação ao outro testado é que, em primeiro lugar, não é necessário definir duas variáveis - apenas uma é definida e é $IFS
e isso é apenas uma vez. Ele também não bifurca um shell filho por iteração - o que geralmente não é uma boa ideia.
Ele depende do mecanismo de substituição em $*
para campos em $@
. Por isso, apenas adiciona um novo posicionamento nulo para cada iteração. Observe também que isso evita a expansão do for {1..1000}
de desperdício.
time \
bash -c '
IFS=\*; set ""
until [ "$#" -gt 1001 ]
do set "" "$@"
echo "$*"
done
' >/dev/null
real 0m0.755s
user 0m0.753s
sys 0m0.000s
Embora seja duas vezes mais rápido que a outra solução somente de shell, ainda é muito terrível.
Isso é um pouco melhor - vai para o outro lado. Em vez de expandir "$@"
para obter os valores desejados, ele é construído de forma exponencial e reduzido de forma incremental:
time \
bash -c '
set \*;n=0 IFS=
until [ "$#" -gt 512 ]
do set "$@" "$@"
shift "$(($#>1000?24:0))"
until [ "$n" -eq "$#" ]
do printf %."$((n+=1))s\n" "$*"
done; done
' >/dev/null
real 0m0.158s
user 0m0.157s
sys 0m0.020s
Para superar minha própria sugestão de shell apenas, muito, (pelo menos com bash
- dash
fazendo o acima e bash
fazendo o abaixo são pescoço e pescoço) :
time \
bash -c 'a=
for i in {1..1000}
do a+=\*
echo "$a"
done
' >/dev/null
real 0m0.020s
user 0m0.017s
sys 0m0.000s
O que é encorajador - parece que bash
otimiza a forma a+=
para realmente fazer um acréscimo em vez de uma reavaliação / reatribuição completa. De qualquer forma, o sed
ainda bate.
O sed
acima não bate awk
, embora:
time \
bash -c '
n=1000 \
awk "BEGIN{OFS=\"*\";for(i=2;i<=ENVIRON[\"n\"]+1;i++){\$i=\"\";print}}"
' >/dev/null
real 0m0.010s
user 0m0.007s
sys 0m0.000s
... que é o mais rápido até agora.
Mas nenhum bateu outro sed
que faz basicamente o que minha nstars
function (você encontrará abaixo) faria se você fizesse nstars 1000
:
time \
bash -c '
printf %01000s |
sed -ne "H;x;:loop
s/\(\n\)./*1/
P;//t loop"
' >/dev/null
real 0m0.007s
user 0m0.000s
sys 0m0.003s
... que é o mais rápido até agora. (quando executado com time nstars 1000 >/dev/null
, o resultado real era .006s ) . Há mais sobre isso abaixo.
Outra solução POSIX:
echo Type some stuff:
sed -ne 'H;x;:loop
s/\(\n\)./*/
P;//!q;t loop'
Cole o acima diretamente no seu terminal, digite qualquer string e pressione Enter . Você verá uma pirâmide de *
s, uma linha para cada caractere digitado. Uma string de 3 caracteres dará três linhas, uma de 4 caracteres imprimirá 4 etc.
sed
é perfeitamente capaz de ler tty input e manipulá-lo como quiser. Nesse caso, ele lê uma linha do usuário, coloca um \n
ewline na cabeça da string lida e recursivamente substitui o \n
ewline e o caractere imediatamente após ele por um *
e o \n
ewline novamente, todo o tempo P
rinting somente até o \n
ewline a cada vez.
Ao fazer isso, o espaço padrão realmente se parece com isso (acabei de trocar o comando P
rint para l
ook) :
here's some stuff
*\nere's some stuff$
**\nre's some stuff$
***\ne's some stuff$
****\n's some stuff$
*****\ns some stuff$
******\n some stuff$
*******\nsome stuff$
********\nome stuff$
*********\nme stuff$
**********\ne stuff$
***********\n stuff$
************\nstuff$
*************\ntuff$
**************\nuff$
***************\nff$
****************\nf$
*****************\n$
A linha superior foi minha entrada. Com o P
, no entanto:
here's some stuff
*
**
***
****
*****
******
*******
********
*********
**********
***********
************
*************
**************
***************
****************
*****************
Se você quiser interrompê-lo em 5, por exemplo, você pode adicionar um /.\{5\}\n/
teste, como este:
echo Type some stuff:
sed -ne 'H;x;:loop
s/\(\n\)./*/
P;//!q;/.\{5\}\n/q
t loop'
Você pode gerar as strings da mesma maneira:
nstars()( n=${1##*[!0-9]*}
shift "$((!!n))"
if [ -n "${n:-$1}" ]
then printf %0"$n"s "$*"
else [ -t 0 ] &&
echo Type some stuff: >&0
head -n 1
fi | sed -ne 'H;x;:loop
s/\(\n\)./*/
P;//t loop'
)
Lá, com isso você pode dar um primeiro argumento numérico e sed
imprimirá as estrelas recursivamente até sua contagem, ou você pode fornecer um ou mais argumentos de string e sed
imprimirá as estrelas para todos os caracteres no string, ou você pode executá-lo em um terminal e ele solicitará uma linha de entrada e caracteres de impressão para isso, ou ele manipulará qualquer outra entrada de arquivo sem avisar.
{ nstars 3
nstars three
echo 3please | nstars
nstars
}
OUTPUT:
*
**
***
*
**
***
****
*****
*
**
***
****
*****
******
*******
Type some stuff:
ok
*
**