Quoting / escaping / issue de expansão em “comando em uma variável”

1

Eu quero executar um comando como este em um script bash:

freebcp <authentication and other parameters> -t "," -r "\r\n"

Quando executado diretamente na linha de comando, o comando funciona conforme o esperado. Mas quando colocado em uma variável em um script bash, ele retorna um erro como este:

Msg 20104, Level 3
Unexpected EOF encountered in bcp datafile

Msg 20074, Level 11
Attempt to bulk copy an oversized row to the server

bcp copy in failed

Quando o comando é colocado em uma variável e aspas duplas são escapadas:

cmd="freebcp ${db_name}.dbo.${table_name} in ${p_file} -S ${server_name} -U ${username} -P ${password} -t \",\" -r \"\r\n\" -c"
'$cmd'

Observação : colocar o seguinte no script funciona conforme o esperado:

'freebcp ${db_name}.dbo.${table_name} in ${p_file} -S ${server_name} -U ${username} -P ${password} -t "," -r "\r\n" -c'

Então, sei que há alguns problemas de citação / escape / expansão, mas não consigo descobrir como corrigi-lo.

Nota 2 : Os parâmetros de quoting -t -r não funcionam

    
por denormalizer 20.01.2015 / 02:40

2 respostas

6

Resposta curta: veja BashFAQ # 50: Estou tentando colocar um comando em uma variável, mas os casos complexos sempre falham! .

Resposta longa: o shell faz a expansão de variáveis no meio do processo de análise de uma linha de comando - notavelmente, depois que ele processa aspas e escapa. Como resultado, colocar aspas e escapes em uma variável não faz o mesmo que tê-los diretamente na linha de comando.

A solução na sua resposta (dobrando os caracteres de escape) funcionará (na maioria dos casos), mas não pelo motivo pelo qual você acha que está funcionando, e isso me deixa bastante nervoso. O comando:

cmd="freebcp ... -t "," -r "\r\n" -c"

É analisado na cadeia de caracteres com aspas duplas freebcp ... -t , seguido pela sequência de caracteres não citada , seguido pela sequência de caracteres com aspas duplas -r , seguida pela sequência sem aspas '\\ r \\ n' (o fato que não é citado é por que você precisava dobrar as fugas), seguido pela cadeia entre aspas duplas '-c'. As aspas duplas que você quis fazer parte da string não são tratadas como parte da string, elas são tratadas como delimitadores que alteram o modo como partes diferentes da string são analisadas (e na verdade têm praticamente o contrário do efeito pretendido ). A razão pela qual isso funciona é que as aspas duplas na verdade não estavam tendo muito efeito no comando original, então reverter o efeito não fez muito. Na verdade, seria melhor removê-los (apenas os internos, no entanto), porque seria menos enganoso sobre o que realmente está acontecendo. Isso funcionaria, mas seria frágil - a única razão pela qual funciona é que você realmente não precisava das aspas duplas para começar, e se você tivesse uma situação (digamos, uma senha ou nome de arquivo com um espaço em que você realmente precisava de citações, você estaria em apuros.

Existem várias opções melhores:

  • Não armazene o comando em uma variável, apenas execute-o diretamente. Armazenar comandos é complicado (como você está descobrindo), e se você realmente não precisa, apenas não o faça.

  • Use uma função. Se você está fazendo algo como executar o mesmo comando sobre & acima, defina-o como uma função e use isso:

    loaddb() {
        freebcp "${db_name}.dbo.${table_name}" in "${p_file}" -S "${server_name}" -U "${username}" -P "${password}" -t \",\" -r \"\r\n\" -c"
    }
    loaddb
    

    Note que eu usei aspas duplas em torno de todas das referências a variáveis - isso é geralmente uma boa higiene de script, caso algum deles contenha espaços em branco, curingas ou qualquer outra coisa que o shell > faz analisar valores de variáveis.

  • Use uma matriz em vez de uma variável simples. Se você fizer isso corretamente, cada argumento de comando será armazenado como um elemento separado da matriz, e você poderá expandi-lo com o idioma "${arrayname[@]}" para obtê-lo intacto:

    cmdarray=(freebcp "${db_name}.dbo.${table_name}" in "${p_file}" -S "${server_name}" -U "${username}" -P "${password}" -t \",\" -r \"\r\n\" -c")
    "${cmdarray[@]}"
    

    Novamente, observe o uso prolífico de aspas duplas; aqui eles estão sendo usados para garantir que os elementos da matriz sejam definidos corretamente, não como parte dos valores armazenados na matriz. Além disso, observe que os arrays não estão disponíveis em todos os shells; verifique se você está usando bash ou zsh ou algo semelhante.

Algumas notas finais: quando você usa algo como:

'somecommand'

as backquotes não estão fazendo o que você acha que são, e na verdade são potencialmente perigosas. O que eles fazem é executar o comando, capturar sua saída e tentar executar essa saída como outro comando . Se o comando não imprimir nada, isso não importa; mas se imprimir algo, é improvável que a saída seja um comando válido. Apenas perca as backquotes.

Por último, dar uma senha como um argumento de comando é insecure - argumentos de comando publicados na tabela de processos (por exemplo, o comando ps pode vê-los) e publicar senhas em locais públicos é uma péssima ideia. freebcp parece não ter nenhuma maneira alternativa de fazer isso, mas eu encontrei um patch que permitirá que ele leia a senha de stdin ( echo "$password" | freebcp -P - ... - note que echo é um shell embutido, portanto seus argumentos não aparecem na tabela de processos). Eu não faço nenhuma reclamação sobre a correção, segurança, etc do patch (especialmente desde que é bastante antigo), mas eu iria verificar se eu fosse você.

    
por 20.01.2015 / 05:14
1

Escapar da barra invertida no \ n \ r parece ter feito o truque

cmd="freebcp ${db_name}.dbo.${table_name} in ${p_file} -S ${server_name} -U ${username} -P ${password} -t "," -r "\r\n" -c"
    
por 20.01.2015 / 04:00