Loop através de uma variável shell separada por vírgulas

109

Suponha que eu tenha uma variável shell Unix como abaixo

variable=abc,def,ghij

Eu quero extrair todos os valores ( abc, defe ghij) usando um loop for e passar cada valor em um procedimento.

O script deve permitir a extração de um número arbitrário de valores separados por vírgula de $variable.

Ramanathan K
fonte
O que exatamente você deseja fazer com a iteração?
SMA
1
Quero passar cada um dos campos (digamos abc) como um argumento para um procedimento.
Ramanathan K

Respostas:

125

Você pode usar o seguinte script para percorrer dinamicamente sua variável, não importa quantos campos ela tenha, desde que sejam separados por vírgulas.

variable=abc,def,ghij
for i in $(echo $variable | sed "s/,/ /g")
do
    # call your procedure/other scripts here below
    echo "$i"
done

Em vez da echo "$i"chamada acima, entre doe donedentro do loop for, você pode invocar seu procedimento proc "$i".


Atualização : O trecho acima funciona se o valor da variável não contém espaços. Se você tiver esse requisito, use uma das soluções que podem mudar IFSe, em seguida, analise sua variável.


Espero que isto ajude.

anacron
fonte
8
Isso não funciona se $variablecontém espaços em branco, por exemplo, variable=a b,c,d=> imprime 4 linhas (a | b | c | d) em vez de 3 (ab | c | d)
Dan
Bem como está sendo adicionado novas citações como ** "value **. você poderia atualizar se há algum regex para remover isso ..
gks
136

Não mexer com IFS
Não chamar o comando externo

variable=abc,def,ghij
for i in ${variable//,/ }
do
    # call your procedure/other scripts here below
    echo "$i"
done

Usando a manipulação de string bash http://www.tldp.org/LDP/abs/html/string-manipulation.html

nérico
fonte
3
O bom disso é que você pode aninhar vários loops usando delimitadores diferentes. Boa sugestão!
Brad Parks,
5
Boa dica para evitar bagunçar IFS!
Laimoncijus
13
Pequeno aviso - se algum dos valores em $icontiver espaços, eles serão divididos (e essa pode ser a razão pela qual é passado como separado por vírgulas em vez de por espaços)
Toby Speight
4
Se uma função anterior alterou a configuração IFS, então isso não funciona ... Altere para isto:for i in ${variable//,/$IFS} do; echo "$i"; done
Joep
2
Recebo a mensagem de erro "Substituição incorreta" quando tento essa abordagem.
Lost Crotchet
55

Se você definir um separador de campo diferente, poderá usar diretamente um forloop:

IFS=","
for v in $variable
do
   # things with "$v" ...
done

Você também pode armazenar os valores em uma matriz e, em seguida, percorrê-la conforme indicado em Como faço para dividir uma string em um delimitador no Bash? :

IFS=, read -ra values <<< "$variable"
for v in "${values[@]}"
do
   # things with "$v"
done

Teste

$ variable="abc,def,ghij"
$ IFS=","
$ for v in $variable
> do
> echo "var is $v"
> done
var is abc
var is def
var is ghij

Você pode encontrar uma abordagem mais ampla nesta solução para Como iterar por meio de uma lista separada por vírgulas e executar um comando para cada entrada .

Exemplos da segunda abordagem:

$ IFS=, read -ra vals <<< "abc,def,ghij"
$ printf "%s\n" "${vals[@]}"
abc
def
ghij
$ for v in "${vals[@]}"; do echo "$v --"; done
abc --
def --
ghij --
fedorqui 'então pare de prejudicar'
fonte
Consulte também stackoverflow.com/questions/918886/… :-) Boa sorte a todos.
shellter
Sim! Eu também pensei sobre isso, apenas que exigia as etapas: a whilepara ler em uma matriz e outra para percorrer os resultados.
fedorqui 'SO pare de prejudicar'
3
Fico feliz quando alguém realmente sabe como usar o shell, ao invés de juntar um monte de binutils.
PeterT de
você tem que IFSvoltar ao que era antes?
pstanton
1
@fedorqui Sim! Essa é a única variável que eu queria percorrer e tornou meu arquivo yaml mais fácil de ler (em vez de uma variável de ambiente LONG, ela tem um monte de linhas)
GammaGames
5
#/bin/bash   
TESTSTR="abc,def,ghij"

for i in $(echo $TESTSTR | tr ',' '\n')
do
echo $i
done

Eu prefiro usar tr em vez de sed, porque o sed tem problemas com caracteres especiais como \ r \ n em alguns casos.

outra solução é definir IFS para um determinado separador

Marek Lisiecki
fonte
1

Aqui está uma solução alternativa baseada em tr que não usa eco, expressa como uma linha.

for v in $(tr ',' '\n' <<< "$var") ; do something_with "$v" ; done

Parece mais arrumado sem eco, mas essa é apenas minha preferência pessoal.

6EQUJ5
fonte
0

Tente este.

#/bin/bash   
testpid="abc,def,ghij" 
count=`echo $testpid | grep -o ',' | wc -l` # this is not a good way
count=`expr $count + 1` 
while [ $count -gt 0 ]  ; do
     echo $testpid | cut -d ',' -f $i
     count=`expr $count - 1 `
done
Karthikeyan.RS
fonte
mas e se eu não tiver certeza sobre o número de campos na variável testpid?
Ramanathan K
Além disso, preciso passar cada campo da variável acima como um argumento para um procedimento. E eu quero fazer isso até que o comprimento da variável se torne zero. (ou seja, todos os campos devem ser passados ​​uma vez para o procedimento de processamento)
Ramanathan K
Não sei sobre o procedimento. A partir deste link você pode obter a contagem dos campos. http://stackoverflow.com/questions/8629330/unix-count-of-columns-in-file. Depois disso, faça o loop for em loop while. Você pode conseguir esse.
Karthikeyan.RS
Isso é para contar os campos de um arquivo, eu acho. E se eu precisar contar os campos em uma variável (suponha que a variável seja testpid = abc, def, ghij)
Ramanathan K
echo a variável e canaliza a saída para o awk. Ou então veja minha resposta atualizada. Pode ser útil.
Karthikeyan.RS
0

Outra solução não usando IFS e ainda preservando os espaços:

$ var="a bc,def,ghij"
$ while read line; do echo line="$line"; done < <(echo "$var" | tr ',' '\n')
line=a bc
line=def
line=ghij
user3071170
fonte
Isso funciona corretamente no bash, porém zsh engole os espaços.
lleaff
0

Esta é minha solução bash pura que não altera o IFS e pode incluir um delimitador regex personalizado.

loop_custom_delimited() {
    local list=$1
    local delimiter=$2
    local item
    if [[ $delimiter != ' ' ]]; then
        list=$(echo $list | sed 's/ /'`echo -e "\010"`'/g' | sed -E "s/$delimiter/ /g")
    fi
    for item in $list; do
        item=$(echo $item | sed 's/'`echo -e "\010"`'/ /g')
        echo "$item"
    done
}
placidwolverine
fonte