GNU/Linux >> Znalost Linux >  >> Linux

Proč přihlašovací shell „sudo -i“ poruší argument řetězce příkazu Here-doc?

V posloupnosti pěti níže uvedených příkazů všechny závisí na jednoduchých uvozovkách, které předají možnou substituci proměnnou nazvanou bash shell spíše než volající shell. Volající uživatel je xx , ale volaný shell bude spuštěn jako uživatel yy . První příkaz nahradí $HOME hodnotou volajícího shellu, protože volaný shell není přihlašovací shell. Druhý příkaz nahradí hodnotu $HOME načtenou přihlašovacím shellem, takže je to hodnota patřící uživateli yy . Třetí příkaz se nespoléhá na hodnotu $HOME a vytvoří soubor v uhádnutém domovském adresáři uživatele yy .

Proč čtvrtý příkaz selže? Záměrem je, že zapisuje stejný soubor, ale spoléhá na proměnnou $HOME patřící uživateli yy aby se ujistil, že skutečně skončí v jejím domovském adresáři. Nechápu, proč přihlašovací shell narušuje chování příkazu here-doc předávaného jako statický řetězec s jednoduchými uvozovkami. Selhání pátého příkazu ověřuje, že tento problém není o substituci proměnné.

[email protected] ~ $ sudo -u yy bash -c 'echo HOME=$HOME'
HOME=/home/xx
[email protected] ~ $ sudo -iu yy bash -c 'echo HOME=$HOME'
HOME=/home/yy
[email protected] ~ $ sudo -u yy bash -c 'cat > /home/yy/test.sh << "EOF"
> script-content
> EOF
> '
[email protected] ~ $ sudo -iu yy bash -c 'cat > $HOME/test.sh << "EOF"
> script-content
> EOF
> '
bash: warning: here-document at line 0 delimited by end-of-file (wanted `EOFscript-contentEOF')
[email protected] ~ $ sudo -iu yy bash -c 'cat > /home/yy/test.sh << "EOF"
> script-content
> EOF
> '
bash: warning: here-document at line 0 delimited by end-of-file (wanted `EOFscript-contentEOF')

Tyto příkazy byly vydány na 64bitovém systému Linux Mint 18.3 Cinnamon, který je založen na Ubuntu 16.04 (Xenial Xerus).

Aktualizace: Aspekt here-doc tento problém pouze zastírá. Zde je zjednodušení problému:

$ sudo bash -c 'echo 1
> echo 2'
1
2
$ sudo -i bash -c 'echo 1
> echo 2'
1echo 2

Proč první z těchto dvou příkazů zachovává konec řádku a druhý nikoli? sudo je společný pro oba příkazy, ale zdá se, že escapuje/filtruje/interpoluje odlišně v závislosti na ničem jiném než na volbě „-i“.

Přijatá odpověď:

Dokumentace pro -i uvádí:

-i (simulovat počáteční přihlášení) spustí shell určený záznamem databáze hesel cílového uživatele jako přihlašovací shell. To znamená, že soubory prostředků specifické pro přihlášení, jako jsou .profile nebo .login, budou čteny shellem. Pokud je zadán příkaz, je předán shellu k provedení prostřednictvím volby shellu -c.

To znamená, že skutečně spustí přihlašovací shell uživatele a poté předá jakýkoli příkaz, který jste zadali sudo pomocí -cna rozdíl od co sudo cmd arg arg obvykle se obejde bez -i volba. Obvykle sudo pouze používá jeden z exec* funguje přímo, aby spustil samotný proces, bez mezilehlého shellu a všechny argumenty prošly přesně tak, jak jsou.

Pomocí -i , nastaví prostředí, spustí uživatelský shell jako přihlašovací shell a rekonstruuje příkaz, který chcete spustit, jako argument na bash -c . Ve vašem případě běží (řekněme přibližně) /bin/bash -c "bash -c ' ... '" (představte si, že fungující citace).

Problém spočívá v tom, jak sudo změní příkaz, který jste napsali, na něco, co -c umí poraditi , vysvětleno v další části. Poslední část obsahuje některá možná řešení a mezi tím je nějaká technika ladění a ověřování

Proč se to děje?

Při předání příkazu do -c , potřebuje nějaké předzpracování, aby to udělalo správnou věc, což sudo dělá před spuštěním shellu. Pokud je například váš příkaz:

sudo -iu yy echo 'three   spaces'

pak je třeba tyto mezery escapovat, aby příkaz znamenal totéž (tj. aby jediný argument nebyl rozdělen na dvě slova). Co skončí spuštěním, je:

/bin/bash -c 'echo three   spaces'

Začněme vaším zjednodušeným příkazem:

sudo bash -c 'echo 1
echo 2'

V tomto případě sudo změní uživatele a poté spustí execvp("bash", ["bash", "-c", "echo 1necho 2"]) (pro vynalezenou doslovnou syntaxi pole).

Související:Jak získat dokončení bash pro aliasy příkazů?

Pomocí -i :

sudo -i bash -c 'echo 1
echo 2'

místo toho změní uživatele a poté spustí execv("/bin/bash", ["-bash", "-c", "bash -c echo\ 1\necho\ 2"]) , kde \ rovná se doslovnému a n je zalomení řádku. Unikl mezerám a novému řádku ve vašem hlavním příkazu tím, že je předchází zpětnými lomítky.

To znamená, že existuje vnější přihlašovací shell, který má dva argumenty:-c a celý váš příkaz, rekonstruovaný do podoby, od které se očekává, že mu shell správně porozumí. Bohužel ne. Vnitřní bash příkaz se nakonec pokusí spustit:

echo 1
echo 2

kde první fyzický řádek končí pokračováním řádku (obrácené lomítko následované novým řádkem), které je zcela odstraněno. Logická linie je pak jen echo 1echo 2 , která nedělá to, co jste chtěli.

Existuje argument, že se jedná o chybu v sudo 's escaping, vzhledem ke standardnímu chování párů zpětné lomítko-nový řádek. Myslím, že by mělo být bezpečné je zde nechat bez útěku.

Totéž se stane pro váš příkaz s dokumentem zde. Běží zhruba takto:

/bin/bash -c 'bash -c cat << "EOF"\012script-content\012EOF\012'

kde

Linux
  1. Skriptování Linuxu:hexadecimální číslo na binární řetězec

  2. Proč spouštět příkaz prostředí Linux s '&'?

  3. Proč uživatel 'bin' potřebuje přihlašovací shell?

  1. Proč Bashrc kontroluje, zda je aktuální shell interaktivní?

  2. Proč „ls“ vyžaduje samostatný proces pro provedení?

  3. Proč spuštění příkazu sudo trvá dlouho?

  1. Proč tento plášťový ropovod vystupuje?

  2. Změna výchozího prostředí v Linuxu

  3. Co znamená syntaxe |&v jazyce shellu?