Escrevendo um comando para um arquivo de script com a cotação apropriada

5

Uma situação que encontrei algumas vezes: Eu tenho uma função shell que faz alguma configuração e invoca um comando mais ou menos arbitrário, como este:

setup_and_run() {
    some_setup_commands
    "$@"
}

Eu quero alterar esta função para também escrever um arquivo de script que eu possa usar para executar novamente o comando depois de fazer a mesma configuração. Eu posso fazer o seguinte, que funciona quando não há argumentos com espaços ou outras coisas estranhas no comando:

setup_and_run() {
    some_setup_commands
    cat >>rerun.sh <<EOF
some_setup_commands
$@
EOF
    "$@"
}

Existe uma maneira padrão de alterar isso para que o script seja escrito com aspas adequadas, em geral, mesmo que alguns argumentos possuam espaços ou outros caracteres especiais?

Por exemplo, com a segunda definição de setup_and_run , se eu executar

$ setup_and_run ls "my cat's \"password\""
my cat's "password"

então rerun.sh conterá

some_setup_commands
ls my cat's "password"

considerando que eu quero que ele contenha algo como

some_setup_commands
ls "my cat's \"password\""

ou

some_setup_commands
ls my\ cat\'s\ \"password\"

ou algo assim.

Em uma pitada, posso terceirizar isso para algo como shlex.quote() do Python, mas estou procurando algo que possa ser feito no próprio shell, se possível, ou pelo menos usando uma ferramenta UNIX padrão mais leve.

    
por David Z 15.12.2017 / 00:39

1 resposta

7

No bash, ksh93 e zsh, você pode usar o escape %q para o printf incorporado .

#!/bin/bash
# also works in ksh93, zsh
setup_and_run() {
    some_setup_commands
    cat >>rerun.sh <<EOF
some_setup_commands
$(printf '%q ' "$@")
EOF
    "$@"
}

No bash, você pode usar o printf -v para gravar a saída em uma variável, e não na saída padrão.

#!/bin/bash
printf -v cmd '%q ' "$@"
setup_and_run() {
    some_setup_commands
    cat >>rerun.sh <<EOF
some_setup_commands
$cmd
EOF
    "$@"
}

No zsh, há um método diferente, que é o q sinalizador de expansão do parâmetro , seguido de j: : para unir os elementos da matriz com um espaço.

#!/bin/zsh
setup_and_run() {
    some_setup_commands
    cat >>rerun.sh <<EOF
some_setup_commands
${(j: :)${(q)@}}
EOF
    "$@"
}

Em sh simples, é mais difícil. Uma maneira confiável de citar uma palavra é colocar aspas simples nela e substituir cada uma das citações por '\'' . Fazer a substituição corretamente não é muito fácil, envolvendo uma chamada externa para sed ou um loop.

#!/bin/sh
quote_simple_command () {
  quoted=
  for raw in "$@"; do
    quoted="$quoted'"
    while case "$raw" in *\'*) true;; *) false;; esac; do
      quoted="$quoted${raw%%\'*}'\''"
      raw="${raw#*\'}"
    done
    quoted="$quoted$raw' "
  done
  printf %s "${quoted% }"
}
setup_and_run() {
    some_setup_commands
    cat >>rerun.sh <<EOF
some_setup_commands
$(quote_simple_command "$@")
EOF
    "$@"
}
    
por 15.12.2017 / 01:23