como posso citar uma expansão de variável dentro de uma string para evitar a divisão de palavras?

8
$ myvar="/path to/my directory"
$ sudo bash -c "cd $myvar"

Nesse caso, como citar $myvar para evitar a divisão de palavras devido aos espaços em branco no valor de myvar ?

    
por Tim 07.05.2018 / 13:48

7 respostas

13

Não há nenhuma divisão de palavras (como no recurso que divide variáveis em expansões sem aspas) nesse código, pois $myvar não é sem aspas.

Há, no entanto, uma vulnerabilidade de injeção de comando, pois $myvar é expandido antes de ser passado para bash . Então, seu conteúdo é interpretado como código bash!

Os espaços nele farão com que vários argumentos sejam passados para cd , não por causa da divisão de palavras , mas porque eles serão analisados como vários tokens na sintaxe do shell. Com um valor de bye;reboot , isso irá reiniciar! ¹

Aqui, você deseja:

sudo bash -c 'cd -P -- "$1"' bash "$myvar"

(onde você passa o conteúdo de $myvar como o primeiro argumento desse script in-line; observe como $myvar e $1 foram citados para seus respectivos shell para evitar a divisão de palavras IFS (e globbing)) .

Ou:

sudo MYVAR="$myvar" bash -c 'cd -P -- "$MYVAR"'

(onde você passa o conteúdo de $myvar em uma variável de ambiente).

É claro que você não conseguirá nada útil executando somente cd nesse script inline (além de verificar se root pode cd para lá). Presumivelmente, você quer aquele script para cd lá e então faz algo mais como:

sudo bash -c 'cd -P -- "$1" && do-something' bash "$myvar"

Se a intenção era usar sudo para poder cd em um diretório ao qual você não tem acesso, então isso não funciona.

sudo sh -c 'cd -P -- "$1" && exec bash' sh "$myvar"

iniciará um bash interativo com seu diretório atual em $myvar . Mas esse shell estará sendo executado como root .

Você poderia fazer:

sudo sh -c 'cd -P -- "$1" && exec sudo -u "$SUDO_USER" bash' sh "$myvar"

Para obter um bash interativo e sem privilégios, com o diretório atual sendo $myvar , mas se você não tiver as permissões para cd nesse diretório, não conseguirá fazer nada nesse diretório, mesmo que seja seu diretório de trabalho atual.

$ myvar=/var/spool/cron/crontabs
$ sudo sh -c 'cd -P -- "$1" && exec sudo -u "$SUDO_USER" bash' sh "$myvar"
bash-4.4$ ls
ls: cannot open directory '.': Permission denied

Uma exceção seria se você tiver a permissão pesquisa para o diretório em si, mas não para um dos componentes de diretório de seu caminho:

$ myvar=1/2
$ mkdir -p "$myvar"
$ chmod 0 1
$ cd 1/2
cd: permission denied: 1/2
$ sudo sh -c 'cd -P -- "$1" && exec sudo -u "$SUDO_USER" bash' sh "$myvar"
bash-4.4$ pwd
/home/stephane/1/2
bash-4.4$ mkdir 3
bash-4.4$ ls
3
bash-4.4$ cd "$PWD"
bash: cd: /home/stephane/1/2: Permission denied

¹ estritamente falando, para valores de $myvar como $(seq 10) (literalmente), haveria a divisão de palavras naturalmente após a expansão dessa substituição de comando pelo bash shell iniciado como root

    
por 07.05.2018 / 13:56
4

Há uma nova (bash 4.4) citando magia que permite fazer mais diretamente o que você quer. Dependendo do contexto maior, usar uma das outras técnicas pode ser melhor, mas funciona nesse contexto limitado.

sudo bash -c "cd ${myvar@Q}; pwd"
    
por 08.05.2018 / 00:19
4

Se você não pode confiar no conteúdo de $myvar , a única abordagem razoável é usar o GNU printf para criar uma versão com escape dele:

#!/bin/bash

myvar=$(printf '%q' "$myvar")
bash -c "echo $myvar"

Como a página printf man diz:

%q

ARGUMENT is printed in a format that can be reused as shell input, escaping non-printable characters with the proposed POSIX $'' syntax.

    
por 07.05.2018 / 18:03
3

Antes de mais nada, como outros notaram seu comando cd é inútil, já que isso acontece no contexto de um shell que sai imediatamente. Mas, em geral, a questão faz sentido. O que você precisa é uma maneira de shell citar uma string arbitrária , para que ela possa ser usada em um contexto onde será interpretada como uma string entre aspas. Eu tenho um exemplo disso na minha página de truques :

quote () { printf %s\n "$1" | sed "s/'/'\\''/g;1s/^/'/;\$s/\$/'/" ; }

Com esta função, você pode fazer:

myvar="/path to/my directory"
sudo bash -c "cd $(quote "$myvar")"
    
por 07.05.2018 / 18:06
3

O que Stéphane Chazelas sugere é definitivamente a melhor maneira de fazer isso. Vou apenas fornecer uma resposta sobre como citar com segurança a sintaxe de expansão de parâmetro, em vez de usar um subprocesso sed , como na resposta da R ...

myvar='foo \'\''"bar'

quote() {
  printf "'%s'" "${1//\'/\'\\'\'}"
}

printf "value: %s\n" "$myvar"
printf "quoted: %s\n" "$(quote "$myvar")"
bash -c "printf 'interpolated in subshell script: %s\n' $(quote "$myvar")"

A saída desse script é:

value: foo \'"bar
quoted: 'foo \'\''"bar'
interpolated in subshell script: foo \'"bar
    
por 07.05.2018 / 22:31
1

Sim, há divisão de palavras. Bem, tecnicamente, a linha de comando se divide no bash analisando a linha.

Vamos primeiro citar corretamente:

O mais simples (e inseguro) de soluções para fazer o comando funcionar como eu acredito que você vai querer é:

bash -c "cd \"$myvar\""

Vamos confirmar o que acontece (assumindo myvar="/path to/my directory" ):

$ bash -c "printf '<%s>\n' $myvar"
<./path>
<to/my>
<directory>

Como você pode ver, a string de caminho foi dividida em espaços. E:

$ bash -c "printf '<%s>\n' \"$myvar\""
<./path to/my directory>

não está dividido.

No entanto, o comando (como escrito) é inseguro. Melhor uso:

$ bash -c "cd -P -- \"$myvar\"; pwd"
/home/user/temp/path to/my directory

Isso protege o cd contra arquivos que começam com um traço (-) ou seguem links que podem causar problemas.

Isso funcionará se você puder confiar que a string em $myvar é um caminho.
No entanto, "injeção de código" ainda é possível quando você está "executando uma string":

$ myvar='./path to/my directory";date;"true'
$ bash -c "printf '<%s> ' \"$myvar\"; echo"
<./path to/my directory> Tue May  8 12:25:51 UTC 2018

Você precisa de uma citação mais strong da string, como:

$ bash -c 'printf "<%s> " "$1"; echo' _ "$myvar"
<./path to/my directory";date -u;"true>

Como você pode ver acima, o valor não foi interpretado, mas usado apenas como "$1' .

Agora podemos usar (com segurança) o sudo:

$ sudo bash -c 'cd -P -- "$1"; pwd' _ "$myvar"
_: line 0: cd: ./path to/my directory";date -u;"true: No such file or directory

Se houver vários argumentos, você poderá usar:

$ sudo bash -c 'printf "<%s>" "$@"' _ "$val1" "$val2" "$val3"
    
por 08.05.2018 / 14:37
-2

As aspas simples não funcionam aqui, pois a sua variável não será expandida. Se você está confiante de que a variável para o seu caminho está desinfetada, a solução mais simples é simplesmente incluir aspas ao redor da expansão resultante, uma vez que está no novo shell bash:

sudo bash -c "cd \"$myvar\""
    
por 07.05.2018 / 13:58