criação dinâmica de instrução de caso

1

Eu tenho uma declaração de caso bastante longa (cerca de 150 padrões para corresponder).

case "$line" in
  pattern1) ret=result1;;
  pattern2) ret=result2;;
  pattern3) ret=result3;;
   ...
   ...
esac

No entanto, isso não é realmente apropriado, já que os padrões são realmente dados modificáveis pelo usuário. Então toda vez que um novo padrão é necessário, eu preciso entrar e modificar a declaração do caso.

O que eu gostaria de fazer é criar um arquivo de texto separado como:

pattern1=result1
pattern2=result2
pattern3=result3
...
...

O arquivo de texto seria lido e usado para criar a declaração de caso. Isso tem a vantagem de não precisar modificar o código toda vez que um novo padrão é necessário.

Existe uma maneira de criar automaticamente uma declaração de caso a partir da entrada de um arquivo de texto? Estou aberto a outras opções, mas estou procurando uma solução que possa fazer o padrão correspondente à linha de entrada muito rápido, pois também tenho um grande número de linhas de entrada para corresponder.

    
por daniel 12.01.2016 / 05:04

3 respostas

2

É uma coisa bastante estranha de se fazer, geração dinâmica de declarações de caso ... mas sim, é possível.

Eu faria algo como o seguinte (se eu tivesse explorado todas outras abordagens em que eu poderia pensar; não sei se eu recomendaria isso):

. <( awk -F= 'BEGIN { print "case \"$line\" in" }
                    { print $1 ") ret=" $2 ";;" }
              END   { print "esac" }' patternfile )

Se patternfile contiver

pattern1=result1
pattern2=result2
pattern3=result3

o comando awk por si só produziria

case "$line" in
pattern1) ret=result1;;
pattern2) ret=result2;;
pattern3) ret=result3;;
esac

e com a construção . <( awk ... ) , isso será originado em seu script, como se você tivesse escrito a opção case diretamente em seu arquivo de script.

Isso responde "criação dinâmica de instrução de caso" em um script de shell. No entanto, como você planeja fazer isso dentro de um loop , o acima seria uma maneira muito ruim de fazer isso, já que você executaria awk 10.000 vezes no seu patternfile , superando assim qualquer benefício de desempenho possível.

Em vez disso, seria melhor escrever o gerador de código para criar um switch de caso dentro de uma definição de função —criar e fornecer uma definição de função inteira, em outras palavras:

# At the top of your script file:
. <( awk -F= 'BEGIN { print "my_case_switch_function() {"
                      print "case \"$1\" in" }
                    { print $1 ") ret=" $2 ";;" }
              END   { print "esac"
                      print "}"   }' patternfile )

# Within your while loop (or wherever else you want):
my_case_switch_function "$line"

Isso permitiria que você reutilizasse a opção case % e mais (10.000 vezes, se preferir) depois de processar apenas patternfile uma vez. Assim, o desempenho é tão bom quanto você obteria se você criasse manualmente o switch de caso do patternfile e codificasse-o em seu script (exceto pela pequena sobrecarga de execução de awk em um arquivo de 150 linhas, que é insignificante próximo ao processamento de 10.000 linhas).

No entanto, deve ser reiterado: as opções de caso de script de shell são não a ferramenta para processar um arquivo de 10.000 linhas linha por linha. Portanto, mesmo que essa solução se aproxime do desempenho que você poderia obter com um switch de caso codificado, provavelmente ainda será lento.

Para citar Stephane Chazelas :

As said earlier, running one command has a cost. A huge cost if that command is not builtin, but even if they are builtin, the cost is big.

And shells have not been designed to run like that, they have no pretension to being performant programming languages. They are not, they're just command line interpreters. So, little optimisation has been done on this front.

    
por 12.01.2016 / 16:40
1

Acho que esse é um problema XY . Você realmente não precisa gerar uma instrução case dinâmica. Você só quer usar um arquivo como um armazenamento simples de valor-chave. Uma solução é pesquisar o arquivo com grep e, em seguida, extrair o valor com cut :

ret=$(grep "^$line" file.txt | cut -d = -f 2)
    
por 12.01.2016 / 08:08
0

Com este arquivo padrão:

a*c=abc
def=def

e assumindo que existe apenas um = por linha:

#! /bin/bash

line=abc
while IFS= read -r patternline; do
        [[ $patternline =~ .=. ]] || continue
        pattern="${patternline%=*}"
        result="${patternline##*=}"
        if [[ "$line" == $pattern ]]; then
                ret="$result"
                echo "$patternline"
                break
        fi
done <patterns.txt

Se muitas linhas precisarem ser verificadas, o conteúdo do arquivo será lido em uma matriz:

#! /bin/bash

patterns_file="patterns.txt"

patterns=()
results=()
patternindex=0


### BEGIN: init
while IFS= read -r patternline; do
        [[ $patternline =~ .=. ]] || continue
        patterns[patternindex]="${patternline%=*}"
        results[patternindex]="${patternline##*=}"
        ((patternindex++))
done <"$patterns_file"
pattern_count="$patternindex"
### END:   init

patterncheck () {
        local line="$1" i=0 pattern= result=
        for((i=0;i<pattern_count;i++)); do
                pattern="${patterns[i]}"
                result="${results[i]}"
                if [[ "$line" == $pattern ]]; then
                        ret="$result"
                        echo "$line"
                        break
                fi
        done
} # patterncheck ()

# tests

patterncheck abc
patterncheck def
    
por 12.01.2016 / 06:30