Citando e escapando

3

Eu tenho um shell script, cujos argumentos são coletados em citação como um único argumento e passados para um script em perl.

/usr/local/API/check_api.sh "-D x.x.x.x -C Test_Internal_Cluster -u user -p pass  -i 300 -l runtime -s list"

que resultam em

/usr/bin/perl /usr/local/check_api.pl -D x.x.x.x -C Test_Internal_Cluster -u user -p pass -i 300 -l runtime -s list

Eu quero dar um argumento como este:

"Test Internal Cluster"

para o script perl.

Quando estou usando

"Test\ Internal\ Cluster"

como o argumento para o script de shell sua análise como

Test Internal Cluster

para o script perl, mas quero que seja entre aspas duplas.

Se eu estiver usando isso:

""\"Test Internal Cluster\"

É convertido para:

'"Test' Internal 'Cluster"'

Isso parece simples, mas não é possível usar citações adequadas e fugir para alcançar.

O script de shell é:

#!/bin/bash -xv

Perl=/usr/bin/perl
Api=/usr/local/API/check_api.pl

if [ $# -gt 0 ]; then
  if [ -x $Api ]
   then
        output=$($Perl $Api $1)
        exitstatus=$?
        F1=$(echo $output | awk -F':' '{print $1}')
        F2=$(echo $output | awk -F':' '{print $2}' | awk -F'|' '{print $1}')
        F3=$(echo $output | awk -F':' '{print $2}' | awk -F'|' '{print $2}')
        echo " $F1: $F3 | $F2"
  else
        echo "check_api.pl not found"
 fi
 exit $exitstatus
else
    echo "Usage : $0 '-D <DC> -C <Cluster> -u <userbame> -p <password> -i <interval> -l <command>  -s <subcommand>'"
fi

Aqui você pode ver todo o argumento passado para o shell script ser coletado para $1 e passado para o script check_api perl.

O problema está acontecendo com o nome do cluster com espaço (passado para -C).

E todo o argumento passado para o script de shell deve estar entre aspas simples / duplas.

Eu não citei a variável $ 1 porque o perlscript não está aceitando o argumento inteiro como um single como abaixo do que encontrei no modo de depuração - onde o argumento inteiro é citado se $ 1 for citado.

/usr/bin/perl /usr/local/check_api.pl '-D x.x.x.x -C Test_Internal_Cluster -u user -p pass  -i 300 -l runtime -s list'

e lança erro como uso incorreto

Mas sem citar o $ 1, o argumento passado para o script perl funciona assim e me dá o resultado:

/usr/bin/perl /usr/local/check_api.pl -D x.x.x.x -C Test_Internal_Cluster -u user -p pass -i 300 -l runtime -s list

Mas eu falhei com um cluster com espaço em seu nome.

No caso de um espaço no nome do cluster, se eu estiver executando diretamente na linha de comando (não do shell script), ele funcionará bem sem qualquer problema:

/usr/bin/perl /usr/local/check_api.pl -D x.x.x.x -C "Test Internal Cluster" -u user -p pass -i 300 -l runtime -s list

que eu queria alcançar a partir do script de shell.

    
por kumarprd 25.02.2014 / 07:57

5 respostas

3

Você deve usar assim:

"\"Test Internal Cluster\""

ou envolva-o em single quote :

'"Test Internal Cluster"'

Teste:

#!/bin/bash

echo $1

Saída:

% bash test.sh "\"Test Internal Cluster\""
"Test Internal Cluster"

Atualizar

O problema aqui é porque você chama perl com $1 , $1 contém espaço em branco, então perl o verá como três argumentos separados.

Para evitar isso, você deve agrupar $1 entre aspas duplas "$1" .

Outra observação é que você deve manipular o argumento $1 no script perl, porque se $1 contiver algum caractere especial, isso causará alguns erros no programa perl. você pode usar quotemeta($ARGV[0]) .

Nota

Eu acho que você não deve passar todos os argumentos como uma string para shell script. Você deve passá-los como normal, exceto o nome do cluster, você ainda tem que envolvê-lo como string. Em seguida, passe-os para o script perl por "$@" em vez de $1 .

    
por 25.02.2014 / 08:04
3

Estou com um pouco de dificuldade em seguir a sua pergunta, e acho que tanto o problema concreto quanto a estranha formulação de sua pergunta derivam da mesma raiz, que é um modelo incorreto de como a análise de argumentos de comando funciona.

Um comando não recebe uma string como argumento, mas uma lista de strings. Todas as citações são realizadas pelo shell.

Se você executar algum dos seguintes comandos do shell

mycommand "foo bar" wibble
mycommand 'foo bar' 'wibble'
mycommand ''''foo" "bar \wib\ble
mycommand       foo\ bar \
                "wibble"

(ou uma miríade de variantes), o comando é executado exatamente com os mesmos argumentos em todos os casos:

  • o argumento 0 é mycommand ;
  • o argumento 1 é a string de 7 caracteres foo bar ;
  • o argumento 2 é a string de 6 caracteres wibble .

A construção do shell $1 não significa “pegue o valor do parâmetro 1”. Significa “pegar o valor do parâmetro 1, dividi-lo em partes separadas por espaços em branco, e interpretar cada peça como um padrão curinga e substituí-la pelos nomes dos arquivos correspondentes”. Sim, é um bocado cheio. Como um princípio geral de programação de shell, sempre coloca aspas duplas em torno de substituições de variáveis e comandos de comando : "$1" , "$some_variable" , "$(some_command)" .

Aqui, dois erros fazem um certo, mas apenas em um caso limitado: você passa um único argumento para o shell script e divide em pedaços separados por espaços em branco para transformá-lo em uma lista de strings para passar como argumentos para o Script Perl. Esta é uma interface não intuitiva e limitada. Ele não permite que você tenha espaços nos argumentos para o script Perl, uma vez que os espaços já são usados para separar argumentos. Você não pode ter as duas coisas. Aspas não ajudam aqui: você pode passar um caractere de aspas no argumento para o shell script, e ele se tornará parte de um argumento para o script Perl. Por exemplo, se você executar um dos

check_api.sh "-C \"Test Internal Cluster\""
check_api.sh '-C "Test Internal Cluster"'
check_api.sh -C\ \"Test Internal Cluster\"

o script de shell é chamado com um único argumento -C "Test Internal Cluster" e o script Perl é chamado com os 4 argumentos -C , "Test , Internal e Cluster" .

A correção é simples, mas requer a alteração da convenção de chamada para o script de shell. O que você tem agora é um bug, você precisa consertá-lo. Chame o script da maneira normal:

/usr/local/API/check_api.sh -D x.x.x.x -C Test_Internal_Cluster -u user -p pass  -i 300 -l runtime -s list
/usr/local/API/check_api.sh -D x.x.x.x -C 'Test Internal Cluster' -u user -p pass  -i 300 -l runtime -s list
/usr/local/API/check_api.sh -D x.x.x.x -C "Test Internal Cluster" -u user -p pass  -i 300 -l runtime -s list

(os dois últimos são equivalentes, já que as aspas simples e duplas só fazem diferença quando \$' aparecerem dentro de7) e consertam o script de shell para usar aspas duplas em torno das substituições de comandos.

if [ -x "$Api" ]
then
    output=$("$Perl" "$Api" "$1")
    exitstatus=$?
    F1="${output%%:*}"; output="${output#*:}"
    output="${output%%:*}"
    F2="${output%%\|*}"; output="${output#*\|}"
    F3="${output%%\|*}"
fi

Substitui o código awk por construções de shell equivalentes, supondo que a saída seja uma única linha.

    
por 26.02.2014 / 04:24
2

O problema está na seguinte linha:

output=$($Perl $Api $1)

Você precisa citar o argumento:

output=$($Perl $Api "$1")

Quando você não cita a variável, a divisão de palavras faz o perl ver vários argumentos.

    
por 25.02.2014 / 08:36
1

O problema, na verdade, é que você deveria estar analisando a opção correta. Parece que talvez você não saiba como, mas não há vergonha nisso.

Usando a análise interna de opções do bash , e citando-as corretamente ao longo do caminho, tudo funcionará como você quer, o que significa que você pode citá-lo normalmente na linha de comando. Não há necessidade de recalcular ou escapar de nada.

#!/bin/bash

PERL=/usr/bin/perl
API=/usr/local/API/check_api.pl

usage() {
    echo "Usage : $0 -D <DC> -C <Cluster> -u <userbame> -p <password> -i <interval> -l <command>  -s <subcommand>"
    exit 1
}

while getopts "C:D:u:p:i:l:s:" options; do
    case $options in
        C ) CLUSTER="${OPTARG}";;
        D ) DC="${OPTARG}";;
        u ) USER="${OPTARG}";;
        p ) PASS="${OPTARG}";;
        i ) INTERVAL="${OPTARG}";;
        l ) COMMAND="${OPTARG}";;
        s ) SUBCOMM="${OPTARG}";;
        * ) usage ;;
    esac
done

$PERL $API -C "$CLUSTER" -D "$DC" -u "$USER" -p "$PASS" -i "$INTERVAL" -l "$COMMAND" -s "$SUBCOMM"

Parece que você está fazendo outras coisas com a saída, o que deixarei para você anexar a este exemplo.

    
por 25.02.2014 / 10:33
1

O problema é que você tenta usar uma única variável para armazenar uma lista de strings:

I have a shell script, the arguments of which are collected in quote as a single argument and passed to a perl script.

Isso não funciona. Uma vez que você concatena vários argumentos e obtém uma única string, a distinção entre espaços dentro de argumentos e espaços entre argumentos é perdida. Não há como consertar isso incluindo a inclusão de caracteres dentro de a string, porque, simplesmente disse, citar personagens perde sua magia assim que eles são parte de uma string. Uma barra invertida ou " dentro de uma string é um caractere comum; não cita nada.

Solução: use uma variável de matriz para coletar os argumentos:

A=()
A+=(-D "$address")
A+=(-C "$clustername")
A+=(-u "$username")
A+=(-p "$password")
...
output=$("$Perl" "$Api" "${A[@]}")

(nota citando.)

Claro, se os argumentos que você quer passar para o programa perl são apenas todos os argumentos da linha de comando que são passados para o seu script, então eles já estão contidos em uma matriz (ou seja, lista de argumentos de linha de comando "$@" ), então você pode simplesmente usar esse:

output=$("$Perl" "$Api" "$@")
    
por 25.02.2014 / 10:33