Durante os últimos 354 anos eu estive splitando linhas em bash assim:
line="root:x:0:0:root:/root:/bin/bash" IFS=":" set -- $line echo "$1" # root echo "$7" # /bin/bash # ou: IFS=":" fields=($line) echo "${fields[0]}" # root echo "${fields[6]}" # /bin/bash
Turns out que faz 354 anos que eu estou fazendo isso errado.
Acontece que expansão de parâmetros (i.e., $var) fora de aspas duplas não só causa word splitting (que é o que queremos), mas também causa pathname expansion (substituição de *, ?, ~ e afins pelos nomes de arquivo que casam com o padrão):
cd / line='a*:b*:c*' IFS=":" fields=($line) echo "${fields[0]}" # a* echo "${fields[1]}" # bin echo "${fields[2]}" # boot echo "${fields[3]}" # c*
Esse problema nunca me ocorreu na prática, mas me dei conta disso depois de escrever o post sobre manipulação de strings em bash. O pior é que eu já sabia há muito tempo que variáveis fora das aspas sofrem pathname expansion, mas acho que adquiri o hábito de splitar strings assim antes de saber disso. Uma solução correta é usar o comando IFS=delimitadores read -a arrayname (que lê uma linha com campos separados por delimitadores e coloca os pedaços na variável arrayname) em conjunto com o operador <<<string (que alimenta a stdin do comando com a string):
line='a*:b*:c*' IFS=":" read -a fields <<<"$line" echo "${fields[0]}" # a* echo "${fields[1]}" # b* echo "${fields[2]}" # c*
De brinde agora é possível incluir : em um campo usando a seqüência \:, já que o read entende o \ como um indicador de que o próximo caractere deve ser interpretado literalmente (a menos que a opção -r (de raw) seja usada).
O último exemplo de splitting do post anterior (que procura o nome do shell de um usuário no /etc/passwd) ficaria assim:
#!/bin/bash user="$1" IFS=":" while read -a campos line; do if [ "${campos[0]}" = "$user" ]; then echo "${campos[6]}" fi done </etc/passwd
Outra feature interessante do read é a opção -d, que permite especificar um caractere diferente de \n como terminador de linha. Isso é útil, por exemplo, em conjunto com o find -print0, que imprime os nomes dos arquivos separados pelo caractere \0 (ASCII NUL) ao invés de \n:
find . -name '*.bkp' -print0 | while IFS= read -d $'\0' file; do echo "Arquivo $file será renomeado." mv "$file" "${file%.bkp}~" done
Isso garante que o script vai funcionar mesmo que o nome de algum arquivo contenha quebras de linha, o que faz com que o script não seja vulnerável ao usuário malandro que criar um arquivo chamado foo\n/bin/bash\nbar.bkp em seu home.
Olá Vitor.
Estou criando um scrip em ash para Busybox, que esta em um dispositivo embarcado, mas ele não fornece suporte para array, existe algum outro jeito de eu separar as palavras de uma string sem usar array. Minha ideia é ler um arquivo com algum texto, separar as palavras e criar pastas com essas palavras.
Obrigado.
@Eric: Se você tem o texto em questão separado por espaços numa variável, se você não colocar espaços em volta da $variável ao usá-la, o shell vai splitar o valor da variável. Por exemplo:
texto="foo bar baz"
mkdir $texto
vai chamar "mkdir foo bar baz", e criar os três diretórios.
Resta o problema de como pegar o texto do arquivo, mas isso você pode fazer, por exemplo, pegando a saída do cat:
mkdir $(cat arquivo-com-palavras.txt)
Ou então, se quiser fazer algo mais complexo com cada arquivo, pode fazer um 'for':
for palavra in $(cat arquivo-com-palavras.txt); do
mkdir $palavra
...
done
O único problema com essa abordagem é que, se alguma palavra contiver '*', '?' ou [], o shell pode acabar expandindo o nome, como explicado neste post. Há maneiras de contornar isso, mas não sei se para o seu caso vale a pena o trabalho.
Qualquer coisa estamos aí. :)
Ola Vitor, bom dia.
Muito obrigado pela resposta.
Vou tentar e ver se funciona, retorno assim que implementar.
Abraços.
Copyright © 2010-2023 Vítor De Araújo
O conteúdo deste blog, a menos que de outra forma especificado, pode ser utilizado segundo os termos da licença Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International.
Powered by Blognir.
Cara, 2013-01-01 22:25:51 -0200 #
Pelo menos são só os últimos 354 anos =D Imagina se tivessem sido todos os teus 2k anos?