Bash

Bash shell 腳本在通過標準輸入傳遞到 LXC 容器時亂序執行

  • December 1, 2015

我將以下簡單的 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/nullapt-get install,然後繼續您的一天。

長答案(說真的,這是一個大槍):從正在執行的程序的角度來看,stdin/stdout/stderr 沒有什麼特別之處。它們只是文件描述符,當它們被分叉時,文件描述符在程序之間共享。所以,發生的事情(或多或少)是:

  1. 在您的終端中以互動方式執行的 bash 副本打開一個新的pipe(2),然後分叉一個新程序,該程序關閉現有的標準輸出,然後使標準輸出文件描述符 (1) 成為管道的寫入端(參見dup2(2)) . 然後該子程序execscat sample.sh讀取文件並將其寫入它認為是標準輸出的內容(但實際上是管道的寫入端)。
  2. 在終端中以互動方式執行的 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 installtra 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,在你之前execapt-get用你從打開得到的文件描述符交換標準輸入(文件描述符0)/dev/null”。

供讀者完成的練習:嘗試</dev/zeroapt-get install命令之後放置,並解釋為什麼會發生這種情況。

引用自:https://serverfault.com/questions/739664