jak mogę zacytować zmienne rozwinięcie w ciągu, aby uniknąć podziału słów?

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

W takim przypadku, jak mogę cytować, $myvaraby uniknąć dzielenia słów z powodu białych spacji w wartości myvar?

Tim
źródło
4
Bez względu na to, jak to rozwiążesz, nadal niewiele zrobi. cdMa tylko wpływ wewnątrz bash -cskorupy.
Kusalananda
Podaję tylko minimalny przykład samego pytania, nie jest to działające rozwiązanie mojego prawdziwego problemu, którym jest unix.stackexchange.com/a/269080/674, a ponadto w podanym ciągu poleceń sudo bash -cmam zmienne rozszerzenie do ścieżka, która może zawierać białe znaki.
Tim

Odpowiedzi:

13

W tym kodzie nie ma podziału słów (jak w przypadku funkcji, która dzieli zmienne przy niecytowanych rozszerzeniach), ponieważ $myvarnie jest ona cytowana.

Istnieje jednak usterka $myvarpolegająca na wstrzykiwaniu poleceń, która jest rozszerzana przed przekazaniem do bash. Jego treść jest interpretowana jako kod bashowy!

Spacje w nich spowodują przekazanie kilku argumentów cd, nie z powodu podziału słów , ale dlatego, że zostaną one przeanalizowane jako kilka tokenów w składni powłoki. Z wartością bye;reboot, to uruchomi się ponownie!

Tutaj chciałbyś:

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

(gdzie można przekazać zawartość $myvarjako pierwszy argument tego skryptu inline; notatka jak zarówno $myvari $1były notowane odpowiednio dla swoich skorupach, aby zapobiec IFS-word-rozszczepienia (i globbing)).

Lub:

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

(gdzie przekazujesz zawartość $myvarzmiennej środowiskowej).

Oczywiście nie osiągniesz niczego użytecznego, uruchamiając tylko cd w tym wbudowanym skrypcie (innym niż sprawdzenie, czy rootmożna cdtam wejść). Prawdopodobnie chcesz, aby ten skrypt cdtam był, a następnie zrób tam coś innego, na przykład:

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

Jeśli chodziło o to, sudoaby móc przejść cddo katalogu, do którego inaczej nie masz dostępu, to tak naprawdę nie może działać.

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

uruchomi interaktywny bashz bieżącym katalogiem w $myvar. Ale ta powłoka będzie działać jako root.

Mógłbyś:

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

Aby uzyskać nieuprzywilejowaną interaktywność bashz bieżącym katalogiem $myvar, ale jeśli nie masz uprawnień do cdtego katalogu, nie będziesz w stanie nic zrobić w tym katalogu, nawet jeśli jest to twój bieżący katalog roboczy.

$ 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

Wyjątkiem może być posiadanie uprawnień do wyszukiwania do samego katalogu, ale nie do jednego ze składników katalogu jego ścieżki:

$ 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

¹ ściśle mówiąc, w przypadku wartości $myvarpodobnych $(seq 10)(dosłownie), po rozszerzeniu tego podstawiania poleceń przez bash powłokę zacząłoby się dzielenie słówroot

Stéphane Chazelas
źródło
4

Pojawiła się nowa magia cytowania (bash 4.4), która pozwala ci bardziej bezpośrednio robić to, co chcesz. W zależności od większego kontekstu użycie jednej z innych technik może być lepsze, ale działa w tym ograniczonym kontekście.

sudo bash -c "cd ${myvar@Q}; pwd"
Seth Robertson
źródło
4

Jeśli nie możesz zaufać zawartości $myvar, jedynym rozsądnym podejściem jest użycie GNU printfdo utworzenia jego ucieczkowej wersji:

#!/bin/bash

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

Jak printfmówi strona podręcznika :

%q

ARGUMENT jest drukowany w formacie, który można ponownie wykorzystać jako dane wejściowe powłoki, unikając znaków niedrukowalnych z proponowaną $''składnią POSIX .

Toby Speight
źródło
Zobacz moją odpowiedź na przenośną wersję tego.
R .. GitHub ZATRZYMAJ LÓD
GNU printfbędzie tam wywoływane tylko w systemach GNU, a jeśli $(printf '%q' "$myvar")zostanie uruchomione w powłoce, w której printfnie jest wbudowane. Większość pocisków ma printfobecnie wbudowane. Nie wszyscy jednak obsługują, %qa kiedy to robią, cytują rzeczy w formacie obsługiwanym przez odpowiednią powłokę, co niekoniecznie musi być takie samo, jak rozumiane przez bash. Właściwie bashnic printfnie zacytować rzeczy prawidłowo nawet sama dla niektórych wartości $myvarw niektórych lokalizacjach.
Stéphane Chazelas
Z bash-4.3co najmniej, że nadal jest to usterka wtrysku komenda z myvar=$'\xa3``reboot`\xa3`' In A zh_HK.big5hkscs locale na przykład.
Stéphane Chazelas,
3

Przede wszystkim, jak zauważyli inni, twoje cdpolecenie jest bezużyteczne, ponieważ dzieje się to w kontekście powłoki, która natychmiast wychodzi. Ale ogólnie pytanie ma sens. Potrzebny jest sposób na zacytowanie powłoki dowolnego łańcucha , aby można go było używać w kontekście, w którym będzie on interpretowany jako łańcuch cytowany w powłoce. Mam taki przykład na stronie ze sztuczkami sh :

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

Dzięki tej funkcji możesz następnie:

myvar="/path to/my directory"
sudo bash -c "cd $(quote "$myvar")"
R .. GitHub ZATRZYMAJ LÓD
źródło
3

To, co sugeruje Stéphane Chazelas, jest zdecydowanie najlepszym sposobem na zrobienie tego. Podam tylko odpowiedź, jak bezpiecznie cytować ze składnią rozszerzania parametrów w przeciwieństwie do korzystania z sedpodprocesu, jak w odpowiedzi 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")"

Dane wyjściowe tego skryptu to:

value: foo \'"bar
quoted: 'foo \'\''"bar'
interpolated in subshell script: foo \'"bar
JoL
źródło
1

Tak, istnieje podział słów.
Cóż, technicznie rzecz biorąc, dzielenie wiersza poleceń podczas bash parsowania linii.

Zacznijmy od właściwej wyceny:

Najprostszym (i niepewnym) rozwiązaniem, aby polecenie działało tak, jak sądzę, będzie to potrzebne:

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

Potwierdźmy, co się stanie (zakładając myvar="/path to/my directory"):

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

Jak widać, łańcuch ścieżki został podzielony na spacje. I:

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

Nie jest podzielony.

Jednak polecenie (jak napisano) jest niepewne. Lepsze wykorzystanie:

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

To ochroni płytę CD przed plikami rozpoczynającymi się od myślnika (-) lub następujących łączy, które mogą prowadzić do problemów.

To zadziała, jeśli możesz zaufać, że ciąg znaków $myvarjest ścieżką.
Jednak „wstrzyknięcie kodu” jest nadal możliwe, ponieważ „wykonujesz ciąg”:

$ 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

Potrzebujesz silniejszego cytowania łańcucha, takiego jak:

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

Jak widać powyżej, wartość nie została zinterpretowana, a jedynie użyta jako "$1'.

Teraz możemy (bezpiecznie) korzystać z sudo:

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

Jeśli jest kilka argumentów, możesz użyć:

$ sudo bash -c 'printf "<%s>" "$@"' _ "$val1" "$val2" "$val3"
Izaak
źródło
-2

Pojedyncze cudzysłowy nie będą tutaj działać, ponieważ wtedy twoja zmienna nie zostanie rozwinięta. Jeśli masz pewność, że zmienna dla twojej ścieżki jest odkażona, najprostszym rozwiązaniem jest po prostu dodać cudzysłowy wokół wynikowego rozszerzenia, gdy znajdzie się w nowej powłoce bash:

sudo bash -c "cd \"$myvar\""
cunninghamp3
źródło
2
Nadal podatność na wstrzykiwanie poleceń, jak dla wartości $myvarpodobnych $(reboot)lub"; reboot; #
Stéphane Chazelas
1
użycie sudo bash -c "cd -P -- '$myvar'"ograniczyłoby problem do tylko tych wartości, $myvar które zawierają znaki pojedynczego cudzysłowu, co byłoby łatwiejsze do oczyszczenia.
Stéphane Chazelas,