Programação de shell, evitando tempfiles

8

Costumo escrever scripts de shell KSH que seguem o mesmo padrão:

  • (1) recuperar a saída de um ou mais comandos
  • (2) formate-o usando grep | cut | awk | sed e imprima-o na tela ou em um arquivo

Para fazer isso, costumo armazenar a saída de (1) em um arquivo temporário e, em seguida, faço a formatação em (2) nesse arquivo.

Tome esse código, por exemplo:

TMPFILE=file.tmp

# If tmpfile exists rm it.
[ -f $TMPFILE ] && rm -f $TMPFILE

for SERVICE in $(myfunc); do
    getInfo $SERVICE > $TMPFILE # Store raw output in the TMPFILE

    # I retrieve the relevant data from the TMPFILE
    SERV_NAME=$(head -1 $TMPFILE | sed -e 's/ $//')
    SERV_HOSTNAME=$(grep HOSTNAME $TMPFILE | cut -d "=" -f2)
    SERV_ARGS=$(grep Arguments $TMPFILE | cut -d ":" -f2)

    print $SERV_NAME $SEP $SERV_HOSTNAME $SEP $SERV_ARGS
    rm -f $TMPFILE #rm the TMPFILE in vue of next iteration
done

Existe uma maneira, usando pipes, redirecionamentos e outras coisas, para evitar gravar um arquivo no disco a cada vez?

Se ajudar, estou usando a versão ksh M-11/16 / 88i

rahmu
fonte
3
É uma boa forma evitar nomes de variáveis ​​ALL_CAPS em scripts de shell e tratar esse espaço de nomes como reservado pelo shell para evitar sobrecarregar coisas importantes, como PATHoutras shell ou variáveis ​​de ambiente. TMPFILEpode ser bom, mas TMPDIRé especial, então você realmente quer andar nessa corda bamba?
Jw013
Para a posteridade: outra pergunta que foi marcada como duplicada desta unix.stackexchange.com/questions/63923/… inclui uma resposta envolvendo fifo pipes nomeados, que também podem ser usados ​​aqui (embora provavelmente não seja a melhor opção em neste caso em particular).
GOLDILOCKS
@ goldilocks: Talvez possamos juntar as duas perguntas em uma. Podemos entrar em contato com um moderador para fazer isso?
Rafael
@rahmu: marquei a outra pergunta. Eu acho que cabe aos poderes que ser agora ...
Goldilocks

Respostas:

9

Seu código parece um exemplo inteiramente justificado de usar tempfiles para mim. Eu ficaria: fique com essa abordagem. A única coisa que realmente precisa ser alterada é a maneira como você cria o arquivo temporário. Use algo como

 TMP=$(tempfile)

ou

 TMP=$(mktemp)

ou pelo menos

 TMP=/tmp/myscript_$$

Dessa forma, você não permitirá que o nome seja facilmente previsto (segurança) e exclua a interferência entre várias instâncias do script em execução ao mesmo tempo.

rozcietrzewiacz
fonte
2
pedanticamente, as cotações não são necessárias para a atribuição de variáveis.
Glenn Jackman
1
@glenn É verdade que, nesse caso, eles não devem fazer diferença, pois cada um dos comandos normalmente produz uma string sem espaços. Mas é um bom hábito ter aspas nos casos em que você atribui a saída do comando a uma variável - por isso vou persistir em deixar dessa maneira.
rozcietrzewiacz
Removidas as aspas no último exemplo para distinção.
rozcietrzewiacz
3
@roz Não, você perdeu o ponto. As atribuições de variáveis ​​no shell são reconhecidas antes de qualquer expansão, e a divisão de campo NÃO é feita para atribuições de variáveis. Assim, var=$(echo lots of spaces); echo "$var"é bom e deve produzir lots of spacescomo saída. A verdadeira advertência que ninguém mencionou é a substituição de comando retira todas as novas linhas finais. Isso não é um problema aqui, e só importa, por exemplo, se você quebrou mktempo sistema e criou nomes de arquivos com novas linhas à direita. A solução usual, se necessário, é var=$(echo command with trailing newline; echo x); var=${var%x}.
jw013
1
@ jw013 Sim, eu percebo isso agora - não, quando escrevi a resposta um ano atrás. Obrigado por apontar isso! (fixação ...)
rozcietrzewiacz
5

Você poderia usar uma variável:

info="$(getInfo $SERVICE)"
SERV_NAME="$(head -1 $TMPFILE <<<"$info" | sed -e 's/ $//')"
...

De man ksh:

<<<word       A  short  form of here document in which word becomes the
              contents of the here-document after any parameter  expan-
              sion,  command  substitution, and arithmetic substitution
              occur.

As vantagens incluem:

  • Habilita a execução paralela.
  • Na minha experiência, isso é muito mais rápido que os arquivos temporários. A menos que você tenha tantos dados que você acaba trocando, eles devem ter ordens de magnitude mais rápidas (exceto os buffers de cache em HD, que podem ser tão rápidos quanto pequenas quantidades de dados).
  • Outros processos ou usuários não podem atrapalhar seus dados.
l0b0
fonte
Parece que o <<< não existe no meu ksh. Eu recebo um erro e não consigo encontrá-lo na página de manual. Estou usando o ksh88. Você tem certeza de que esta versão deve ter esse recurso?
rahmu
Não; Eu acho que eu não verificar o direito manda página (não havia nenhuma menção do número de versão na página web: /)
l0b0
<<<é bash 'aqui string'. Acho que não aparece em nenhuma outra concha. (Oh, zshtalvez ...)
rozcietrzewiacz
2
@rozcietrzewiacz: Google para man ksh. Certamente foi mencionado lá.
L0b0 29/09/11
3
Adivinhe como o bash implementa strings here e here-docs. sleep 3 <<<"here string" & lsof -p $! | grep 0rsleep 30251 anthony 0r REG 253,0 12 263271 /tmp/sh-thd-7256597168 (deleted)- sim, ele usa um arquivo temporário.
Derobert
2

Você tem duas opções:

  1. Você recupera os dados uma vez (no seu exemplo com getInfo) e os armazena em um arquivo como faz.

  2. Você busca os dados a cada vez e não os armazena localmente, ou seja, liga getInfosempre

Não vejo o problema ao criar um arquivo temporário para evitar o reprocessamento / nova busca.

Se você estiver preocupado em deixar o arquivo temporário em algum lugar, sempre poderá trapexcluí-lo, caso o script seja interrompido / interrompido

trap "rm -f $TMPFILE" EXIT HUP INT QUIT TERM

e use mktemppara criar um nome de arquivo exclusivo para seu arquivo temporário.

Matteo
fonte
1

Em vez de gerar um arquivo, construa instruções de atribuição de shell e avalie essa saída.

for SERVICE in $(myfunc); do
    eval $(getInfo $SERVICE |
               sed -n -e '1/\(.*\) *$/SERV_NAME="\1"/p' \
                   -e '/HOSTNAME/s/^[^=]*=\([^=]*\).*/SERV_HOSTNAME="\1"/p' \
                   -e '/Arguments/^[^:]*:\([^:]*\).*/SERV_ARGS="\1"/p')
    print $SERV_NAME $SEP $SERV_HOSTNAME $SED $SERV_ARGS
done

Ou se você quiser apenas imprimir as informações:

for SERVICE in $(myfunc); do
    getInfo $SERVICE | awk -vsep="$SEP" '
        BEGIN{OFS=sep}
        NR == 1 { sub(/ *$/,""); SERV_NAME=$0 }
        /HOSTNAME/ { split($0, HOST, /=/; SERV_HOSTNAME=HOST[2]; }
        /Arguments/ { split($0, ARGS, /:/; SERV_ARGS }
        END { print SERV_NAME, SERV_HOSTNAME, SERV_ARGS }'
done
Arcege
fonte