Ajuda com manipulação de strings no shell

2

Eu estava escrevendo um script de shell para gerar uma string em um formato específico para que possa ser usado como uma entrada em um dos XML com os quais estou trabalhando.

Dado um arquivo de entrada no formato <attribute field>,<data_type>,<size>

instanceid,varchar,256
sysdate,date
status,number
notes,varchar,4000
created_on,date

Eu quero armazenar em uma variável "check sum" como md5( INSTANCEID || STATUS || NOTES) . Isso é que eu quero todos os campos de atributos, exceto o campo que tem data como é tipo Ou.

O script que eu escrevi é este

IFS=$'\n'
file=$(cat source.txt)
line_number=$(cat source.txt | wc -l)
checksum="md5( "
for line in $file
do
let line_number=line_number-1
data_field=$(echo $line | cut -f1 -d','| tr "a-z" "A-Z")
data_type=$(echo $line | cut -f2 -d',' | tr "a-z" "A-Z")
if [ $data_type != "DATE" ]  && [ $line_number -gt 0 ]
  then checksum+="$data_field || "
elif [ $data_type != "DATE" ] && [ $line_number -eq 0 ]
  then checksum+=" $data_field "
fi
done
checksum+=")"
echo $checksum

Esse script funciona bem com todos os cenários de entrada, exceto quando a última linha tem um atributo com data como seu tipo.

Nesse caso, a variável tem um valor como md5( INSTANCEID || STATUS || NOTES || )

Eu tentei verificar se a última linha era uma data usando o comando tail , mas isso falharia novamente se as últimas linhas tivessem o tipo como data.

Como posso eliminar o || que aparece no final?

    
por thebenman 19.05.2015 / 19:51

3 respostas

3

A resposta rápida é checksum="${checksum% || })" em vez de checksum+=")" . Apenas adicione incondicionalmente a string || em cada etapa e, em seguida, retire a última desnecessária no final (para que a computação line_number não seja mais necessária).

A melhor maneira de fazer isso é

awk -F, 'BEGIN { printf "md5( " } 
         toupper($2) != "DATE" { printf "%s%s", sep, toupper($1); sep = " || " }
         END { print ")" }' source.txt
    
por 19.05.2015 / 21:04
1
  • É notável como raramente cat é útil em um script de shell. $(cat source.txt | wc -l) é um clássico uso inútil de cat ; se você precisasse contar as linhas em um arquivo, $(wc -l < source.txt) é uma maneira muito mais limpa de fazer isso.
  • Mas você não precisa contar as linhas em source.txt .
  • file=$(cat source.txt) é uma maneira feia de ler um arquivo;
    while read …
    do
        ︙
    done < filename
    é melhor. read tem o benefício de poder dividir linhas em campos para você.
  • É bobinho executar tr duas vezes para cada linha do arquivo quando você precisa apenas executá-lo uma vez no arquivo inteiro. Em algumas situações,
    tr … < filename | while read …
    do
        ︙
    done
    funciona bem. Mas há um problema com isso: o loop while é executado em um subshell, Assim, as alterações feitas nas variáveis do shell (por exemplo, checksum ) não será visível depois que o loop terminar. Terdon mostra uma maneira de contornar esse problema; aqui está mais um:
    tr … < filename | { while read …
    do
            ︙
        commands that potentially change checksum.
            ︙
    done
        ︙
    commands that use $checksum.
        ︙
    }
  • Como você descobriu, identificar a última ocorrência de algo pode ser difícil. Geralmente, é mais fácil identificar o primeiro:

    checksum="md5("
    first=1
    tr "a-z" "A-Z" < source.txt | { while IFS=, read data_field data_type size
    do
        if [ "$data_type" != "DATE" ]
        then
            if [ "$first" ]
            then
                first=
            else
                checksum+=" || "
            fi
            checksum+="$data_field"
        fi
    done
    checksum+=")"
    echo "$checksum"
    }
    

    Note que você realmente não precisa testar if [ "$data_type" != "DATE" ] duas vezes.
    Observe também que você deve sempre citar referências às variáveis do shell (por exemplo, "$data_type" ), a menos que você tenha um bom motivo para não e você tem certeza de que sabe o que está fazendo.

  • Como uma otimização adicional, você pode eliminar a variável first e simplesmente usar checksum para identificar sua primeira iteração através do loop:

    checksum=
    tr "a-z" "A-Z" < source.txt | { while IFS=, read data_field data_type size
    do
        if [ "$data_type" != "DATE" ]
        then
            if [ "$checksum" != "" ]
            then
                checksum+=" || "
            fi
            checksum+="$data_field"
        fi
    done
    checksum="md5($checksum)"
    echo "$checksum"
    }
    
por 20.05.2015 / 06:35
0

Você não precisa de nada tão complicado quanto o que você escreveu. Você poderia apenas fazer:

#!/usr/bin/env bash

checksum="md5("
## Read each line into the fields array (read -a fields), with fields
## separated by commas (IFS=,)
while IFS=, read -a fields
do
    ## If the 2nd element of the array is not "DATE"
    if [ ${fields[1]} != "DATE" ]
    then
        ## Add this to $checksum
        checksum+="${fields[0]} || "
    fi
## The tr is making everything upper case and then feeds
## directly into the while loop.
done < <(tr "a-z" "A-Z" < "$1")
## Get rid of the last || and add the closing ")"
checksum="${checksum% || })"
printf "OUT is: %s\n" "$checksum"

Você, então, executa o script com seu arquivo como entrada:

$ foo.sh file
OUT is: md5(INSTANCEID || STATUS || NOTES)
    
por 20.05.2015 / 00:45