Ajuda com "while read $line" e variáveis [RESOLVIDO]

1. Ajuda com "while read $line" e variáveis [RESOLVIDO]

Matheus Britto
Aimagem

(usa Slackware)

Enviado em 09/01/2013 - 17:30h

Pessoal, boa tarde.

Meu problema é o seguinte: Tenho uma pasta com várias sub-pastas e pilhas de arquivos dentro delas. Periodicamente, preciso renomeá-los para "nomedapastas_nomedasubpastas_nomedoarquivo.ext" e movê-los para um servidor FTP. Tenho um script que faz isso de forma primorosa, mas apenas se indico as pastas que vão ser executadas. Isso faz com que tenha que rodar 3 ou 4 scripts diferentes (um para cada pasta). Minha idéia inicial foi trabalhar com a criação de um log e usar o comando cat e while read, mas me deparei com uma questão. Como fazer para ele identificar o início da linha e comparar para ver se roda a ação A ou B? Abaixo, dou um exemplo.

Backup (raíz)
--FotoAlta (subdir 1)
----Recife (subdir 2)
------DSC_0001.jpg
----Sampa (subdir 2)
------DSC_0002.jpg
--FotoMedia (subdir 1)
----Recife (subdir 1)
------DSC_0001.jpg
----Sampa (subdir 2)
------DSC_0002.jpg
--FotoBaixa (subdir 1)
----Recife (subdir 1)
------DSC_0001.jpg
----Sampa (subdir 2)
------DSC_0002.jpg

Em cada subdir 1 terão várias subdir 2 e em cada subdir 2 terão várias imagens.
Preciso que o arquivo final saia com o nome de (exemplo): FotoAlta_Recife_DSC_0001.jpg, FotoAlta_Sampa_DSC_0002.jpg e assim por diante.

Comecei pensando no seguinte:


#!/bin/bash

temp="/.temp"
dest="/WebDav"
server="/Backup"

log="backup.log"

find $server -type f -iname "*.[jJ][pP][gG]" | cut -c 8- > $log
# dessa forma, pego apenas os arquivos JPG e corto a entrada /Backup no log

cat $log | while read line
do

filename="${line##*/}"
# me retornou apenas o nome do arquivo.

subend="${line%/*}"
substart="${subend##*/}"
subdir="${substart}"
# me retornou o nome correto da sub-pasta sem nenhuma /.

dirend="${line%/*/*}"
dirstart="${dirend#/}"
dir="${dirstart}"
# me retornou o nome correto da pasta sem nenhuma /.


Obs.Se fizer da seguinte forma, como chamo essa rotina? dir="subdir_name" ?


subdir_name ()
{
end="${line%/*/*}"
start="${end#/}"
dir="${start}"
}


O que preciso, e ainda não sei como fazer, é testar se o nome da variável $dir é igual ao nome que procuro e depois rodar as ações.
seria algo tipo:


if [ $dir == "FotoAlta" ]
then
mv $line $log $dest/"dir"_"subdir"_"filename"
fi


E como fazer para várias pastas? Estou usando 3 como exemplo, mas tenho cerca de 6 pastas distintas (3 para jpg e 3 para RAW).


If
then
comandos
elif
comandos
else
comandos
fi


ou testar para cada nome de pasta?


If
then
comandos
if


Agradeço desde já qualquer ajuda que possam me dar, pois já estou queimando a mufla faz alguns dias.

Abraços,

Matheus.


  


2. Re: Ajuda com "while read $line" e variáveis [RESOLVIDO]

Paulo
paulo1205

(usa Ubuntu)

Enviado em 10/01/2013 - 02:19h

Você pode fazer a variável IFS igual a "/", declarar uma variável qualquer como array, e usar "read -a" para que cada componente do path seja atribuído a um elemento desse array. Por exemplo:

ORIG_IFS="$IFS"
IFS=/
declare -a components
exec 3< <(find /some_dir -type d -print)
while read -a components <&3; do
# Testa se o path tem mais de um componente
if [[ ${#components[@]} -gt 1 ]]; then
# Tem, então testa o valor do segundo (arrays começam com 0) componente
if [[ ${components[1]} = "alguma_coisa" ]]; then
# faz alguma coisa
fi
fi
done
exec 3<&-
IFS="$ORIG_IFS"



3. Re: Ajuda com "while read $line" e variáveis [RESOLVIDO]

Matheus Britto
Aimagem

(usa Slackware)

Enviado em 10/01/2013 - 04:59h

Paulo,

Poderia explanar melhor? Estou beeem enferrujado (mais de 15 anos sem programar).

Obrigado assim mesmo.


4. Re: Ajuda com "while read $line" e variáveis [RESOLVIDO]

Paulo
paulo1205

(usa Ubuntu)

Enviado em 10/01/2013 - 10:37h

Relendo sua mensagem, acho que não entendi suas dúvidas. Quando você falou em 6 diretórios, eu entendi que você teria vários níveis de diretórios, um dentro do outro, e que estivesse com problemas em separar as partes quando apareciam mais níveis. AGora já acho que o(s) problema(s) era(m) outro(s), só não entendi bem qual(is).

Se você quer transformar algo como abcd/efgh/ijkl.jpg, armazenado na variável var, em abcd_efgh_ijkl.jpg, pode simplesmente fazer ${var////_}.

De resto, não entendi sua dúvida com relação à função, mas funções no shell têm argumentos recebidos como $*, e comportamento parecido com um comando externo, i.e.: a passagem de argumentos se faz simplemente chamando o nome da função, e a forma de dar um valor de retorno é capturar aquilo que tiver sido impresso. Desse modo, uma função que calcula o máximo divisor comum teria o seguinte formato, seguido de formas de colocar esse resultado em variáveis.

mdc() {
# Testa os dois argumentos numericos recebidos para ver qual o maior e qual o menor
if (( $1>$2 )); then
maior=$1; menor=$2
else
maior=$2; menor=$1
fi

# Se o menor valor nao for zero, faz a chamada recursiva usando o resto da divisao
# do maior pelo menor.
if (( menor )); then
mdc $menor $(( maior % menor ))
else
echo $maior
fi
}

mdc 25 10 # imprime '5' na tela
mdc_9_3=$(mdc 9 3) # atribui o valor 3 a variavel
mdc_105_13=`mdc 105 13` # atribui o valor 1 a variavel



5. Re: Ajuda com "while read $line" e variáveis [RESOLVIDO]

Matheus Britto
Aimagem

(usa Slackware)

Enviado em 10/01/2013 - 15:57h

Paulo,

Acredito que tenha me expressado mal quando formulei o post e tentarei fazer algumas correções agora.

O grande X da questão, pelo menos no meu caso, é que tenho uma pasta raiz, com 6 sub-pastas e dentro de cada sub-pasta outras tantas sub-pastas contendo os arquivos que preciso acessar. A grosso modo, a árvore ficaria parecida com isso:
/Backup/FotoAlta/Recife/DSC_0001.jpg.

Inicialmente, fiz o seguinte:


#!/bin/bash

log="/.log"
temp="/.temp"
dest="/WebDAV"
glob="/Backup"

[[ -z $(shopt -s nullglob; printf %s $glob) ]] && exit

find $glob -type f -iname '*.[jJ][pP][gG]' | cut -c 9- | while read line
do
dirname="${line%/*/*}"
dirname="${dirname#/}"
dirname="${dirname}"

subdir="${line%/*}"
subdir="${subdir##*/}"
subdir="${subdir}"

basename="${line##*/}"
filename="$dirname"_"$subdir"_"$basename"
done


Mas porque dessa forma e não somente com o comando que você me informou (que aliás, funciona da mesma forma e muito bem)? Porque preciso testar se $dirname é igual ao nome que quero e depois executar os comandos que preciso. Tipo:


if [ $dirname == "FotoAlta" ]
then
mv "$glob/$line" $dest/"filename"
fi


Agora os problemas.
1. O que me chamou a atenção, foi que dessa forma (com o if puro) se digo que ele vai enviar essas fotos por mail (uso o sendEmail) ele entra em um loop estranho e termina enviando várias vezes a mesma fotos. Se tivessem 4 fotos, ele enviaria 4 mails com 4 fotos, pois para cada foto ele executaria o comando de enviar as fotos com aquele nome.

2. Fazer uma função. Pensei que se tivesse uma função para checar a quantidade de arquivos e enviar apenas os que existirem de uma única vez, podendo passar a informação de qualquer nome para ele checar. No caso, esse nome seria o $dirname. Não sei ao certo se isso seria uma boa ou seria melhor fazer de outra forma, mas foi a primeira coisa que me veio à cabeça. Minha outra dúvida quanto às funções e, como chamá-las, refere-se à seguinte situação hipotética:


function NameFile ()
{

find /Teste -type f -iname '*.[jJ][pP][gG]' | cut -c 9- | while read line
do
dirname="${line%/*/*}"
dirname="${dirname#/}"
dirname="${dirname}"

subdir="${line%/*}"
subdir="${subdir##*/}"
subdir="${subdir}"

basename="${line##*/}"
filename="$dirname"_"$subdir"_"$basename"
done
}


Como eu chamaria o $filename dentro da função para o comando mv "$glob/$line" $dest/"$filename" ? Sei que se se o while read line estiver dentro da função não irá funcionar, mas tem como contornar isso? Gerando um arquivo de log e usando o comando cat $files | while read line funcionaria? Como?

Acho que agora ficou um pouco mais fácil de entender, mas se ainda ficar alguma dúvida, berra ai que tento explicar mais.

Obrigado pela ajuda.

Abraços.


6. Re: Ajuda com "while read $line" e variáveis [RESOLVIDO]

Paulo
paulo1205

(usa Ubuntu)

Enviado em 11/01/2013 - 20:25h

Alguns comentários iniciais sobre pedaços do script que não necessariamente estão ligados ao seu problema principal:

1) dirname="${dirname}" e subdir="${subdir}" são atribuições inócuas (seria como fazer, em C, a=a;). Essas linhas podem ser removidas sem medo nem pena.

2) O predicado "-iname" já indica ao find que a comparação dos nomes dos arquivos encontrados com o padrão deve ser feita considerando letras maiúsculas e minúsculas como idênticas, indepedentemente de como o padrão esteja expresso e de como seja formado o nome do arquivo. Desse modo, você pode colocar seu padrão simplesmente como "*.jpg".

3) Embora funcione para você porque você faz tudo dentro do loop, a construção "comando | while read ..." é algo cujo uso eu desencorajo. O motivo para tanto é o fato de qe possíveis variáveis que tenham seus valores modificados no interior do loop não refletem tais alterações quando o loop termina.

Na verdade, o problema é um pouco mais sutil, e não reside no loop, mas no fato de que, com o bash, o comando que está à direita numa pipeline roda num processo separado, mesmo que seja um comando interno como while ou read, e nada do que roda no processo separado sobrevive no processo original. Então, se voc&#7869; fizer "a=5; echo 10 | read a; echo $a", a saída impressa será "5". Do mesmo modo, a saída correspondente a "a=0; echo 1 | while read a; do echo -n "$a, "; done ; echo $a" será "1, 0").

Essa construção tem, aliás, um outro problema, que também não fica evidente na sintaxe: o redirecionamento da entrada não afeta somente o comando que vem logo depois do sinal "|", mas tudo o que executar no mesmo subprocesso ou em qualquer um dos seus processos filhos. Desse modo, alguém poderia ficar surpreso quando vir que a construção abaixo não esperar a resposta, embora exiba as perguntas, sobre se os arquivos devem ser removidos ou não (o exemplo é tosco e ineficiente, mas é só para demonstrar; lembrando que a opção "-i" de rm serve para pedir confirmação antes de remover um arquivo).

find /algum_diretorio -name \*.jpg | while read file; do
rm -i $file # Também vai ler da saída do comando find!
done


O tosco exemplo acima poderia ser reescrito de forma um pouco mais segura mudando a forma de fazer o redirecionamento, o que permitiria ao loop executar no processo original, em lugar de criar o subprocesso que seria criado com uma pipeline.

# Use o file descriptor 3 para interceptar a saida do find.
exec 3< <(find /algum_diretorio -name \*.jpg)

# Le os nomes de cada arquivo a partir do FD 3. Note que só o read
# é redirecionado, não o while, e não se cria outro processo para isso.
while read file <&3; do
rm -i $file # Agora a confirmação virá do FD 0, que não foi redirecionado
done

# Fecha o FD 3
exec 3<&-


-----------------

O seu if não parece ter coisa alguma errada. Entendo que ele deveria estar dentro do loop, logo após a definição de todas as variáveis. Que erro houve quando você o usou?

Não conheço o sendEmail, logo não imagino por que motivo você está recebendo mensagens quadriplicadas. Minha sugestão seria você colocar o comando "echo" na frente da linha em que invoca o sendEmail, para que o comando que seria executado seja impresso na tela. Se for impresso apenas uma vez, você saberá que também o sendEmail não estava sendo invocado mais do que uma vez, e os motivos da multiplicação das mesnagens poderia estar até mesmo no processo de envio de e-mail. Já se for impresso várias vezes para cada linha, você provavelmente tem algum erro no script que não fica evidente nas postagens que você fez aqui (até porque você não mostrou coisa alguma com o sendEmail).

Com relação a funções, não vi ganho em mover o loop para dentro da função, a não ser que você tenha em mente algum código bem maior do que os pedaços que postou aqui. De todo modo, a não ser que você declare como locais as variáveis que usar dentro da função, elas são visíveis com o mesmo nome e valor fora dela depois que a função acabar de executar (i.e. toda variável é global em princípio). Caso queira, pode passar à função um argumento que é o nome de uma variável que você queira modificar, e usar o comando eval para atribuir um valor à variável com tal nome, como mostrado abaixo.

func () {
eval "$1=5"
}

a=0
func a
echo $a # vai imprimir "5"



7. Re: Ajuda com "while read $line" e variáveis [RESOLVIDO]

Matheus Britto
Aimagem

(usa Slackware)

Enviado em 11/01/2013 - 23:20h

Paulo,

Que belíssima aula você me deu. Muitas coisas nem tinham passado pela minha cabeça e, agora, novos caminhos se abriram. Agradeço todos os dias pela comunidade *nix ser tão bem relacionada e aberta à ajudar aos que tem dúvidas.

Sobre a redundância do dirname="${dirname}" e subdir="${subdir}", já havia reparado isso durante a madrugada e retirado do script. Mas é sempre bom ter esse retorno sobre o assunto. O sendEmail (http://caspian.dotconf.net/menu/Software/SendEmail/) é uma alternativa bem interessante para quando se quer/precisa enviar anexos de forma fácil pelo shell, principalmente se utilizado em um script. Não sei porque não coloquei a linha do sendEmail aqui antes, mas ponho agora e passo novas informações sobre o andamento do script "maluco" que estou fazendo.

Em vias gerais, podemos chamar o sendEmail da seguinte forma:
De todas as formas que testei ele foi o que me retornou a melhor solução para o script.


sender="me@mail.com"
receiver="you@mail.com"
smtp="smtp.com:25"
user="me@mail.com"
pass="mypassword"
files="file.ext"

sendEmail -f $sender -t $receiver -u "Subject" -m "Body" -a "$files" -s $smtp -xu $smtpuser -xp $smtppass


Ainda sobre ele, o erro estava onde fiz a chamada para ele e não nele em si.


for file in $temp
do

# variáveis dos posts anteriores #

if [ $dirname = "FotoAlta" ]
then
sendEmail -f $sender -t $receiver -u "Subject" -m "Body" -a "$files" -s $smtp -xu $smtpuser -xp $smtppass
fi
done


Dessa forma, claro que ele entraria em um loop mais doido que eu. Depois de um tempo foi que notei onde estava o erro. Para cada arquivo dentro de $temp, ele enviaria um mail com os arquivos que começassem com FotoAlta. Fazer as coisas sem prestar atenção dá nisso. §:-P Esse problema já foi corrigido e funciona super bem agora. O que fiz foi o seguinte:


temp="/temp"
serv="Backup"
glob=`find $serv -type d`

for file in $glob
do

find $serv -type f -iname '*.jpg' | cut -c 10- | while read line
do
filename="${line////_}"

mv "$serv/$line" $temp/"$filename"
done

done

for file in $temp
do

ls -1 $temp | while read line
do
dirname="${line%%_*}"

if [ $dirname = "FotoAlta" ]
then
sendEmail
fi
done
done


E ai testo cada começo de arquivo e envio para cada endereço correspondente, seja ele mail ou ftp.

Agora preciso confessar um coisa para você. Fazia um bom tempo que não me divertia tanto na frente do computador, digitando linhas de comando. Me senti com 15 anos novamente…

Paulo, mais um vez muitíssimo obrigado pela ajuda e dicas primorosas.

Abraços,

Matheus.






Patrocínio

Site hospedado pelo provedor RedeHost.
Linux banner

Destaques

Artigos

Dicas

Tópicos

Top 10 do mês

Scripts