Saída de piping para arquivo de texto dentro de um loop for

4

Estou tentando fazer o seguinte em um loop for :

  • Encontre arquivos que satisfaçam uma condição
  • Eco o nome dos arquivos para um arquivo de log.
  • Gzip o arquivo.

Eu posso pegar o script para encontrar os arquivos e fazer eco de seus nomes para a tela, mas não posso canalizá-los para um arquivo. (Eu não cheguei tão longe testando a parte do gzip)

Aqui está a parte relevante do script (O $LOGCLEAN_FILE existe e está escrito em uma parte anterior do script):

for F in 'find . -type f \( ! -name '*.gz' \) -a \( ! -name '*.Z' \) -mtime +7'
do

        {
        print "Will be compressing file  ${F}" >> $LOGCLEAN_FILE
        } ; 
        ##gzip $F
done

Se eu executar o script sem o texto " >> $LOGCLEAN_FILE" , a saída será exibida na tela.

O que estou fazendo de errado?

As críticas são bem-vindas - ainda estou aprendendo.

    
por morgan_g 15.03.2013 / 16:09

5 respostas

4

Realmente não há necessidade de reproduzir a saída de find em um loop de shell. Se você quiser compactar a lista de nomes de arquivos , a fórmula genérica é:

find ... | gzip > logfile.gz

Se você deseja gzipar os arquivos , ele muda para:

find ... | tar -czvf archive.tar.gz -T -

que informa tar para ler a lista de nomes de arquivos para trabalhar em um arquivo e o único - significa entrada padrão. (A opção -T AKA --files-from= está presente no GNU tar, não tenho certeza sobre outros sabores.) É claro que isso quebra se você conseguir trabalhar em arquivos que contenham \n em seus nomes.

    
por 15.03.2013 / 16:15
4

Você não pode pós-processar a saída do find de forma confiável. Use -exec em find :

find . -type f ! -name '*.gz' ! -name '*.Z' -mtime +7 -exec sh -c '
  for i do
    printf "%s\n" "Will be compressing file $i"
    gzip "$i"
  done' sh {} + >> log

Com a implementação GNU de find , você pode até fugir sem executar sh:

find . -type f ! -name '*.gz' ! -name '*.Z' -mtime +7 \
  -printf 'Will be compressing file %p\n' -exec gzip {} + >> log
    
por 15.03.2013 / 21:40
2

coloque seu redirecionamento ( >> ) fora do loop (depois de done ) da seguinte forma:

for i in $(find . -type f \( ! -name '*.gz' \) -a \( ! -name '*.Z' \) -mtime +7);
  do echo "Will be compressing file  ${i}";
  ##gzip $i;
done >> $LOGCLEAN_FILE

Ou, se você planeja eventualmente descomentar seu comando gzip , considere:

for i in $(find . -type f \( ! -name '*.gz' \) -a \( ! -name '*.Z' \) -mtime +7);
  do gzip "$i" && echo "Successfully compressed ${i}";
done >> $LOGCLEAN_FILE

o && significa somente fazer o próximo comando se o comando anterior sair com 0 (sem erro).

    
por 15.03.2013 / 16:33
1

Como nomes de arquivos podem ter tanto espaços quanto novas linhas, uma abordagem provável seria:

#!/bin/bash

logclean_file="logclean_file.txt"

ts="$(date)"

printf "%s\n" "$ts" > "$logclean_file"

# Set IFS blank
# -r Backslash does not act as an escape character. The  backslash
#    is  considered  to be part of the line.  In particular, a
#    back-slash-newline pair may not be used as a line continuation.
# -d delim
#    The  first  character  of  delim is used to terminate the input
#    line, rather than newline.
# 
# Here setting -d to nul or 0x00. This enables us to capture any file-
# names with the print0 from find.
#
# fn The variable to read into.
#
while IFS= read -r -d $'\x00' fn; do
    printf "Will perhaps be compressing file %s\n" "$fn" >> "$logclean_file"
    # If file + gz does not exist 
    if [[ ! -e "$fn.gz" ]]; then
            if gzip command "$fn"; then
                    echo "Horray! success!" >> "$logclean_file"
            else
                    echo "Harf! Gzip failed." >> "$logclean_file"
            fi
    else
            echo "Nah. Already exists." >> "$logclean_file"
    fi
done < <(find . -type f \( ! -name '*.gz' \) -a \( ! -name '*.Z' \) -print0)
# Notice -print0 at end which means find will print filenames, - separating
# them with 0x00 instead of new-line

Sobre portabilidade:

Nem echo nem print são realmente portáteis. Embora print seja exclusivo de ksh93 , echo só foi padronizado muito tarde no processo POSIX, e versões mais antigas não podem ser usadas para produzir resultados previsíveis. Consulte echo vs print e Por que printf é melhor ... .

printf "Some %s\n" "$var" >> "$foo"
# Or
echo "Some $var" >> "$foo"

Sobre o estilo:

  • Variáveis quote .
  • Considere o uso de minúsculas de variáveis do usuário (suas próprias variáveis).

Alguns ref:

Para os guias do TLDP, e se você estiver usando elegante , eu recomendaria um de estes para leitura de tela.

    
por 15.03.2013 / 17:15
0

Se você está tentando canalizar para um arquivo de saída, iterando sobre caminhos de arquivos ...

Use os recursos exec do find

... para processar suas iterações.

Eu queria fazer uma iteração por meio de uma lista de arquivos explícitos via subshell, e chamar expand , que gera stdout, e sobrescrever cada arquivo individual. Um problema abordado por esta resposta .

find $(git ls-files | grep '.py$') \
    -exec bash -c 'expand -t 4 "$0" > /tmp/e && mv /tmp/e "$0"' {} \;

Eu estou repondo uma versão ligeiramente modificada dessa resposta, já que as outras respostas neste tópico não fornecem uma solução de propósito geral. Esta é a única coisa que vi que funciona, que só se aplica ao iterar sobre caminhos de arquivos.

Posso imaginar uma solução que cria arquivos com o nome de seus itens de iteração de modo abusivo, para que você possa iterar esse nome usando a localização.

Falha na tentativa:

Assim como a chamada find exec, tentei usar bash -c ... dentro do loop for. Mas eu corri para o mesmo problema de arquivos de saída vazios.

Disclaimer: Eu não tenho certeza qual foi exatamente o meu problema, em todas as tentativas antes de usar o find exec, todos os arquivos de saída que eu esperava serem gravados, foram sobrescritos, meu problema era que eles eram arquivos vazios depois.

    
por 01.02.2015 / 03:43

Tags