Como criar uma sequência de loop?

5

Estou tentando criar um script que receberá uma variável do usuário e deverá imprimir uma pirâmide conforme abaixo:

*
**
***
****
*****

Eu usei esse script, mas ele mostra números:

for i in {1..5}

do

a=${a}${i}

echo ${a}

done

a saída:

1
12
123
1234
12345

Como posso inserir o sinal "*" em vez de números?

    
por Invoker 08.06.2015 / 23:48

4 respostas

8

Basta anexar o caractere * à variável a , em vez do contador de loop:

for i in {1..5}
do
  a+='*'
  echo "${a}"
done

Observe que a="${a}*" em vez de a+='*' funciona tão bem, mas acho que a versão += é mais pura / clara.

Se você quiser fazer isso com um loop while, você pode fazer algo assim:

while (( "${#a}" < 5 )); do
  a+='*'
  echo "$a"
done

${#a} expande para o tamanho da string na variável a .

Observe que os dois trechos de código acima (assim como o código na pergunta) assumem que a var esteja vazio ou não definido no início do snippet. Se esse não for o caso, será necessário reinicializá-lo primeiro:

a=

Estou assumindo que você está usando o shell bash. Aqui está o manual completo. Aqui está o section em construções de looping.

    
por 08.06.2015 / 23:53
5
A resposta do Digital Trauma é mais eficiente neste caso, mas apenas para o seu bem estar completo poderia usar o método shell tradicional de repetir caracteres usando printf :

for i in {1..5}
do
  printf "%${i}s\n"
done | sed 's/ /*/g'

Isso usa printf para produzir quantos espaços forem necessários e, em seguida, sed para substituir os espaços pelo caractere que realmente queremos.

Como apontado por Trauma Digital e Janis , você pode armazenar o resultado de printf em uma variável e usar a substituição de subcadeia do shell para evitar o uso de sed (ao custo de gerar lotes de subshells em muitos casos):

for i in {1..5}
do
  a=$(printf "%${i}s")
  echo "${a// /*}"
done

As aspas são necessárias com echo aqui para evitar que o shell interprete os caracteres * . Além disso, com echo , o \n não é mais necessário.

Como apontado por kojiro , tr é mais adequado para substituir caracteres únicos do que sed :

for i in {1..5}
do
  printf "%${i}s\n"
done | tr ' ' '*'

(Mas a variante sed permite repetir texto de qualquer tamanho.)

    
por 08.06.2015 / 23:59
4

POSIXly:

$ n=5 awk 'BEGIN{OFS="*";for(i=2;i<=ENVIRON["n"]+1;i++){$i="";print}}'
    
por 09.06.2015 / 08:53
2

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
*
**
    
por 09.06.2015 / 11:35