Bash shell 腳本在通過標準輸入傳遞到 LXC 容器時亂序執行
我將以下簡單的 shell 腳本傳遞給 LXC 容器上的 bash:
apt-get update apt-get install postgresql -y sudo -u postgres psql -c 'create database dvdrental;'
我用來執行它的實際命令是:
cat sample.sh | lxc-attach -n test-container -- /bin/bash
我這樣做而不是將腳本上傳到容器並以這種方式執行的原因是,這只是我們正在建構的一個更複雜的應用程序的概念證明,它必須通過標準輸入接收命令並執行它們在容器中。
它似乎工作得很好,除了一件事。
psql
它在 postgresql 仍在安裝時移動到命令,即[...] Get:21 http://archive.ubuntu.com/ubuntu/ trusty/main ssl-cert all 1.0.33 [16.6 kB] Get:22 http://archive.ubuntu.com/ubuntu/ trusty-updates/main postgresql-common all 154ubuntu1 [103 kB] Get:23 http://archive.ubuntu.com/ubuntu/ trusty-updates/main postgresql-9.3 amd64 9.3.10-0ubuntu0.14.04 [2,669 kB] Get:24 http://archive.ubuntu.com/ubuntu/ trusty-updates/main postgresql all 9.3+154ubuntu1 [5,038 B] Fetched 5,834 kB in 28s (207 kB/s) Preconfiguring packages ... sudo -u postgres psql -c 'create database dvdrental;' Selecting previously unselected package libroken18-heimdal:amd64. (Reading database ... 14599 files and directories currently installed.) Preparing to unpack .../libroken18-heimdal_1.6~git20131207+dfsg-1ubuntu1.1_amd64.deb ... Unpacking libroken18-heimdal:amd64 (1.6~git20131207+dfsg-1ubuntu1.1) ... Selecting previously unselected package libasn1-8-heimdal:amd64. [...]
注意
sudo -u postgres psql -c 'create database dvdrental;'
輸出中間的行的存在。有趣的是,它總是在 apt-get 命令的下載部分完成後立即顯示…有誰知道這可能是什麼原因造成的?
哦哦,這很有趣。
簡短的回答:它發生在那裡是因為 apt (或它派生的東西)在其執行時正在讀取標準輸入,並且它讀取腳本的其餘行,因為那是當時仍在標準輸入中的內容。簡短的解決方法:放在行
</dev/null
尾apt-get install
,然後繼續您的一天。長答案(說真的,這是一個大槍):從正在執行的程序的角度來看,stdin/stdout/stderr 沒有什麼特別之處。它們只是文件描述符,當它們被分叉時,文件描述符在程序之間共享。所以,發生的事情(或多或少)是:
- 在您的終端中以互動方式執行的 bash 副本打開一個新的
pipe
(2),然後分叉一個新程序,該程序關閉現有的標準輸出,然後使標準輸出文件描述符 (1) 成為管道的寫入端(參見dup2
(2)) . 然後該子程序exec
scat sample.sh
讀取文件並將其寫入它認為是標準輸出的內容(但實際上是管道的寫入端)。- 在終端中以互動方式執行的 bash 副本分叉了另一個新程序,這次關閉現有的stdin,然後使 stdin 文件描述符 (0) 成為前面討論過的同一管道的讀取器端(再次呼叫
dup2
)。這個過程就是exec
你的lxc-attach
過程。如果一路上沒有任何東西干擾標準輸入(在這種特殊情況下它不會),那麼從將管道的讀取器端作為標準輸入的程序派生的每個程序**也將具有完全相同的文件描述符,附加到與它的標準輸入相同的管道,其中
sample.sh
填充了內容。 從該文件描述符讀取的任何程序現在都將消耗讀取的字節,並且從該文件描述符讀取的任何其他程序都不會獲得這些特定字節。請注意這一點;您將再次看到此材料。 3. 當你的意大利馬桶式管道盛宴的遠端的 bash 終於開始時,它會從管道中讀取“一些”數據,這是它的標準輸入(因為這就是 bash 在沒有參數和沒有參數的情況下呼叫時所做的事情一個 tty 作為標準輸入)。通過 的魔力strace
,我剛剛確認 bash 實際上一次讀取一個字元(而不是讀取它,比如 4k 塊),所以每個不屬於 bash 的命令的單個字元,或目前正在執行,仍將位於管道中,其中-bash-has-as-its-stdin。 4. 當 bash 執行腳本中的第二個命令apt-get install
tra la la 時,它會派生一個新程序。它繼承了 bash 的所有文件描述符,包括(最重要的是)我們的好朋友 pipe-which-is-stdin。這種情況也會發生在任何分叉的程序apt-get
(我向你保證,很多)。其中之一或apt-get
它本身決定讀取標準輸入並將它讀取的任何內容寫入標準輸出(或可能是標準錯誤)。 5. 完成apt-get install
後,bash 通過再次讀取 stdin 來找出接下來要執行的操作。但是,因為其他東西已經從管道中讀取了所有內容,所以什麼都沒有了,並且 bash 表示“哦,好吧,我想我已經完成了”並退出。再一次,管道是空的,因為其他東西已經讀乾了它,並且共享單個文件描述符的所有內容都共享了它的賞金。不出所料,“共享標準輸入”問題的解決方案是停止在兄弟會聚會上像煙槍一樣傳遞標準輸入。由於您無法阻止
fork
(2) 自動為每個人提供相同的文件描述符,因此您需要告訴 bash 給apt-get
(以及其他任何從該甜蜜、甜蜜的管道中非法啜飲的東西)其他東西來代替. 最容易給予的是/dev/null
——永遠忠實、永遠不完整,你所有“戴夫不在這裡,伙計”的快樂源泉。那是“輸入重定向”的域,這就是它的</dev/null
作用——它說,“嘿,bash,在你之前exec
,apt-get
用你從打開得到的文件描述符交換標準輸入(文件描述符0)/dev/null
”。供讀者完成的練習:嘗試
</dev/zero
在apt-get install
命令之後放置,並解釋為什麼會發生這種情況。