Posso usar uma variável em uma expansão de chave Bash?

10

Abaixo está algum tipo de pseudo-código para o que estou tentando realizar:

#!/bin/bash

# I already have the variable below figured out (positive integer):
numlines=$([returns number of lines containing specific characters in a file])

# This is basically what I want to do with it:
for i in {1..$numlines}; do
    # the part below is already figured out as well:        
    do some other stuff
done

Eu posso executá-lo bem na linha de comando, inserindo o número real na sequência `{1..n} '. Eu só preciso saber se é possível incluir uma variável aqui e como proceder.

  • Eu tentei exporting-lo
  • Eu tentei colocar a variável em si entre chaves dentro da sequência: {1..${numlines}}
  • Eu tentei colocá-lo entre aspas duplas na esperança de expandir: {1.."$numlines"}
  • Eu tentei escapar do $:{1..\$numlines}

Preciso usar um set -[something]comando para que essa variável seja expandida? Eu até tentei algumas formas de usar eval... tudo sem sucesso.

Eu só preciso saber se há algo simples ou obscuro que estou perdendo ou se isso é possível antes que eu perca mais tempo com isso.

Eu poderia criar uma maneira realmente, muito tola de fazê-lo, para fazê-lo funcionar conforme necessário, mas gostaria de evitar isso, se possível, e aprender o caminho certo para fazê-lo.

rubynorails
fonte

Respostas:

11

Infelizmente, não há como usar uma variável nessa expansão (AFAIK), uma vez que a expansão variável ocorre após a expansão entre chaves.

Felizmente, há uma ferramenta que faz o mesmo trabalho.

for i in $(seq 1 $numlines); do
    # stuff
done

seqé do GNU coreutils; não faço ideia de como fazê-lo no POSIX.

Tom Hunt
fonte
1
(Mais 1). seqé bom para sistemas GNU e, se bem me lembro, o OSX mais recente. Em outros sistemas BSD, pode-se usar jot .
John1024
seqfunciona perfeitamente. Muito obrigado pela sua pronta resposta.
perfil completo de Rubynorails
Isso não fazia parte da minha pergunta - mas qual é a sintaxe (se houver) para fazer isso na ordem inversa, como {16..1}? $(seq $numlines 1)não funcionou. Eu acho que posso sempre man seq, mas apenas me perguntando se alguém sabia do que se pensava.
perfil completo de Rubynorails
1
Só descobri como fazê-lo em sentido inverso a partir dessa ligação -for i in $(seq $numlines -1 1)
rubynorails
seq ${numlines} -1 0
DopeGhoti
10

Certo. Se você deseja um loop for que incrementa uma variável inteira, use a forma do forloop que incrementa uma variável inteira (ou, geralmente, executa aritmética nas variáveis ​​do loop).

for ((i=1; i<=numlines; i++)); do  done

Essa construção funciona no bash (e ksh93 e zsh), mas não no sh simples. No sh simples, use um loop while e a [ … ]construção test ( ).

i=1
while [ "$i" -le "$numlines" ]; do
  
  i=$((i+1))
done
Gilles 'SO- parar de ser mau'
fonte
8

Se você deve evitar seq, o que, como Tom Hunt aponta, parece ser a solução usual para isso, então evaldefinitivamente pode fazê-lo (embora eu não o incentive):

eval 'for i in {1..'$numlines'}; do echo $i; done'

Você pode permanecer no POSIX evitando a expansão {} e simplesmente fazendo comparações matemáticas e inteiras em $numlines:

while [ ! "$numlines" -eq 0 ]; do
     echo "$numlines"
     : $((numlines-=1))
done

Do lado de fora da POSIX, bashe kshe zshtambém tem de estilo C forlacetes:

for((i=0; i<numlines; i++)); do echo $i; done
PSkocik
fonte
1
Eu realmente aprecio essa resposta também. Embora tenha seqfuncionado bem no meu cenário e pareceu ser a solução mais simples, é bom saber que existem outras alternativas (até POSIX). Obrigado por isso.
amigos estão dizendo sobre rubinorails
2
Realmente não há razão para usar eval; se você tiver expansão de chaves, você tem o loop de estilo C.
Chepner
@PSkocik - Se eu pudesse escolher 2 respostas, também escolheria essa. Quando me deparei com o fato de que precisava fazer isso de maneira inversa, seu evalexemplo era o mais simples e teria me poupado de ter que procurar uma maneira alternativa de fazer isso usando seq. O whileloop é um pouco volumoso para mim. Eu gosto de manter as coisas curtas e agradáveis, e nunca consegui que o forloop no estilo C funcionasse, fornecendo io valor de 0 ou 1. Ele nunca retornou corretamente e sempre estava um pouco fora. Tenho certeza de que poderia ser ajustado para funcionar corretamente, mas essas são definitivamente soluções úteis, no entanto.
rubynorails
A evalabordagem é problemática se houver algo não trivial dentro do corpo do loop. Eu imagino que não seria muito legível se você precisasse aninhar dois desses loops.
kasperd