作者 主題: linux管道的执行顺序  (閱讀 5052 次)

0 會員 與 1 訪客 正在閱讀本文。

biao007h

  • 懷疑的國中生
  • **
  • 文章數: 70
    • 檢視個人資料
linux管道的执行顺序
« 於: 2016-05-04 10:32 »
最近有个疑问,netstat  -antup|head  -500   类似这条命令中,是netstat 执行完然后截取前500条记录还是,netstat 与head 并行执行,netstat 执行完500条就不再继续?

darkranger

  • 榮譽學長
  • 俺是博士!
  • *****
  • 文章數: 1370
    • 檢視個人資料
    • http://darkranger.no-ip.org
Re: linux管道的执行顺序
« 回覆 #1 於: 2016-05-04 12:02 »
先執行完再截取
如果有想到 tail 這個指令的話
或許這個疑問就不會產生了....

dark

  • 俺是博士!
  • *****
  • 文章數: 1581
    • 檢視個人資料
Re: linux管道的执行顺序
« 回覆 #2 於: 2016-05-04 13:29 »
導向是先開啟後面檔案資源
管線恐怕也是 ... 但未向站上老師們請教證實

因為小弟常會抓數十萬行 , 所以總會記得要 head -5 一下
代碼: [選擇]
# date ; (for i in {1..10};do echo $i ;sleep 1; done) | tail -1 ; date
三  5月  4 13:19:36 CST 2016
10
三  5月  4 13:19:46 CST 2016
# date ; (for i in {1..10};do echo $i ;sleep 1; done) | head -1 ; date
三  5月  4 13:19:55 CST 2016
1
三  5月  4 13:19:56 CST 2016
由上似乎控制權在 head 手中 ...


Yamaka

  • 俺是博士!
  • *****
  • 文章數: 4913
    • 檢視個人資料
    • http://www.ecmagic.com
Re: linux管道的执行顺序
« 回覆 #3 於: 2016-05-04 13:47 »
我測試後的結果
感覺像是...
head 如果已經得到所需的資料
管線左邊還在執行的指令會直接被丟棄

引用
$ perl -e 'my $i; for ($i=1; $i<=300000; $i++) { print "$i\n"; }  `notify-send "Done! i=$i"`'|tail -5
299996
299997
299998
299999
300000

桌面右上角有出現訊息『Done! i=3001』

引用
$ perl -e 'my $i; for ($i=1; $i<=300000; $i++) { print "$i\n"; }  `notify-send "Done! i=$i"`'|head -5
1
2
3
4
5

桌面右上角沒出現訊息

biao007h

  • 懷疑的國中生
  • **
  • 文章數: 70
    • 檢視個人資料
Re: linux管道的执行顺序
« 回覆 #4 於: 2016-05-04 17:24 »
多谢各位大大,小弟也试了下,应该是head取完值就不执行了
[root@cqhdtest ~]# time  (for i in {1..10};do echo $i ;sleep 1; done)|tail -1
10

real   0m10.029s
user   0m0.013s
sys   0m0.006s
[root@cqhdtest ~]# time  (for i in {1..10};do echo $i ;sleep 1; done)|head -1
1

real   0m1.008s
user   0m0.001s
sys   0m0.005s

darkranger

  • 榮譽學長
  • 俺是博士!
  • *****
  • 文章數: 1370
    • 檢視個人資料
    • http://darkranger.no-ip.org
Re: linux管道的执行顺序
« 回覆 #5 於: 2016-05-04 17:30 »
唔,我先前都沒有想過這個問題呢....
查了一下再加上一些驗證後
我想管線命令的執行狀況大概是這樣:
1.
管線命令會觸發一個緩衝區(buffer)的建立,讓不同程式從中讀取、寫入資料
2.
管線最末端的程式結束時會觸發緩衝區停止擴充
3.
管線前端的程式則會因為無法再寫入至緩衝區、發生錯誤而終止

測試程式:
代碼: [選擇]
#!/usr/bin/python
# -*- coding: utf-8 -*-
import sys
p = 0

txt = open("log.txt", "w")

while p < int(sys.argv[1]):
    p = p + 1
    txt.write("%d\n" % p)
    print(p)

txt.close()

程式列印 1000 次,儘管 head 後只顯示 5,但 log.txt 仍寫入了 1000 行:
代碼: [選擇]
./pipe_test.py 1000 |head -5
程式列印 3000 次,出現 IOError,log.txt 內則寫入了 2680 行:
代碼: [選擇]
./pipe_test.py 3000 |head -5
代碼: [選擇]
IOError: [Errno 32] Broken pipe
若改寫成忽略 IOError:
代碼: [選擇]
#!/usr/bin/python
# -*- coding: utf-8 -*-
import sys
p = 0

txt = open("log.txt", "w")

while p < int(sys.argv[1]):
    p = p + 1
    txt.write("%d\n" % p)

    try:
        print(p)
    except IOError as error:
        pass

txt.close()

則 log.txt 可以順利寫入到 3000 行

biao007h

  • 懷疑的國中生
  • **
  • 文章數: 70
    • 檢視個人資料
Re: linux管道的执行顺序
« 回覆 #6 於: 2016-05-04 17:39 »
唔,我先前都沒有想過這個問題呢....
查了一下再加上一些驗證後
我想管線命令的執行狀況大概是這樣:
1.
管線命令會觸發一個緩衝區(buffer)的建立,讓不同程式從中讀取、寫入資料
2.
管線最末端的程式結束時會觸發緩衝區停止擴充
3.
管線前端的程式則會因為無法再寫入至緩衝區、發生錯誤而終止

測試程式:
代碼: [選擇]
#!/usr/bin/python
# -*- coding: utf-8 -*-
import sys
p = 0

txt = open("log.txt", "w")

while p < int(sys.argv[1]):
    p = p + 1
    txt.write("%d\n" % p)
    print(p)

txt.close()

程式列印 1000 次,儘管 head 後只顯示 5,但 log.txt 仍寫入了 1000 行:
代碼: [選擇]
./pipe_test.py 1000 |head -5
程式列印 3000 次,出現 IOError,log.txt 內則寫入了 2680 行:
代碼: [選擇]
./pipe_test.py 3000 |head -5
代碼: [選擇]
IOError: [Errno 32] Broken pipe
若改寫成忽略 IOError:
代碼: [選擇]
#!/usr/bin/python
# -*- coding: utf-8 -*-
import sys
p = 0

txt = open("log.txt", "w")

while p < int(sys.argv[1]):
    p = p + 1
    txt.write("%d\n" % p)

    try:
        print(p)
    except IOError as error:
        pass

txt.close()

則 log.txt 可以順利寫入到 3000 行
ho li high!! :o

netman

  • 管理員
  • 俺是博士!
  • *****
  • 文章數: 17464
    • 檢視個人資料
    • http://www.study-area.org
Re: linux管道的执行顺序
« 回覆 #7 於: 2016-05-04 20:25 »
有高手解說... 拜~~~ Orz

netman

  • 管理員
  • 俺是博士!
  • *****
  • 文章數: 17464
    • 檢視個人資料
    • http://www.study-area.org
Re: linux管道的执行顺序
« 回覆 #8 於: 2016-05-04 20:34 »
我也用 shell 來驗證一下:
代碼: [選擇]
kenny@kenny-thinkpad-x230:~/tmp$ cat 1.sh
#!/bin/bash
LANG=C
n=0
for ((i=1;i<=10;i++)); do
echo $i
((n++))
done
echo n:$n >&2
沒帶管線執行:
代碼: [選擇]
kenny@kenny-thinkpad-x230:~/tmp$ ./1.sh
1
2
3
4
5
6
7
8
9
10
n:10
可見迴圈之後的 echo n:$n 會被執行...

然後加上管線:
代碼: [選擇]
kenny@kenny-thinkpad-x230:~/tmp$ ./1.sh | head -n 5
1
2
3
4
5
輸出5行就結束了最後的 echo n:$n 沒有執行...

若將 head 的取值拉大:
代碼: [選擇]
kenny@kenny-thinkpad-x230:~/tmp$ ./1.sh | head -n 15
1
2
3
4
n:10
5
6
7
8
9
10
則可以輸出 echo n:$n ,但似乎是不需要等 stdout 完成,stderr 就輸出了...
而且詭異的是,如果反覆執行,stderr 輸出的時機居然會改變:
代碼: [選擇]
kenny@kenny-thinkpad-x230:~/tmp$ ./1.sh | head -n 5
1
2
3
4
5
kenny@kenny-thinkpad-x230:~/tmp$ ./1.sh | head -n 15
1
2
3
4
5
6
7
8
9
10
n:10
kenny@kenny-thinkpad-x230:~/tmp$ ./1.sh | head -n 15
n:10
1
2
3
4
5
6
7
8
9
10
« 上次編輯: 2016-05-04 20:42 由 netman »

dark

  • 俺是博士!
  • *****
  • 文章數: 1581
    • 檢視個人資料
Re: linux管道的执行顺序
« 回覆 #9 於: 2016-05-05 20:22 »
看來這問題比想像中有意思

原理應如 darkranger 大大所貼
而 netman 網大的試驗
又透露出環境也有影響
是 OS 還是硬體晶片運算 ?

記得有些晶片有預先處理的傳說
但應不至於能到系統這層吧 ...


rainday

  • 鑽研的研究生
  • *****
  • 文章數: 738
  • 性別: 男
  • enhancing and optimizing
    • 檢視個人資料
Re: linux管道的执行顺序
« 回覆 #10 於: 2016-05-20 06:55 »
這個應該是在sh輸出pipe給head 在處理速度上的問題

以netman大的例子,會有個問題是在於 echo n:$n >&2 這時echo是redirect
1
2
3
4
5
6
7
8
9
10
n:10

以strace來看系統的動作
strace -s 128 ./1.sh 可看到並非連輸出到stdout ,  <--- 所以有可能head認為1~10時己經結束 , n:10就要看來不來得及被head接到 , 如果n:10導向的速度快,就有可能插入到head的輸出中,所以實際上head接了二次輸入, 因此head輸出了第一和第二個輸入才會在不特定行數中輸出
如果第二個輸入前head就結束程序, n:10就lost掉了

read(255, "#!/bin/bash\nLANG=C\nn=0\n#echo \"---$n\"\nfor ((i=1;i<=10;i++)); do\n\techo $i\n\t((n++))\ndone\necho n:$n >&2\n", 100) = 100
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 18), ...}) = 0
write(1, "1\n", 21
)                      = 2
write(1, "2\n", 22
)                      = 2
write(1, "3\n", 23
)                      = 2
write(1, "4\n", 24
)                      = 2
write(1, "5\n", 25
)                      = 2
write(1, "6\n", 26
)                      = 2
write(1, "7\n", 27
)                      = 2
write(1, "8\n", 28
)                      = 2
write(1, "9\n", 29
)                      = 2
write(1, "10\n", 310
)                     = 3
fcntl(1, F_GETFD)                       = 0
fcntl(1, F_DUPFD, 10)                   = 10
fcntl(1, F_GETFD)                       = 0
fcntl(10, F_SETFD, FD_CLOEXEC)          = 0   <---
dup2(2, 1)                              = 1   <---
fcntl(2, F_GETFD)                       = 0   <---
write(1, "n:10\n", 5n:10 <---
)                   = 5
dup2(10, 1)                             = 1
fcntl(10, F_GETFD)                      = 0x1 (flags FD_CLOEXEC)
close(10)                               = 0
read(255, "", 100)                      = 0

如果把最後的>&2拿掉就會連續輸出 , head得到的結果就會準確
read(255, "#!/bin/bash\nLANG=C\nn=0\n#echo \"---$n\"\nfor ((i=1;i<=10;i++)); do\n\techo $i\n\t((n++))\ndone\necho n:$n\n", 96) = 96
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 18), ...}) = 0
write(1, "1\n", 21
)                      = 2
write(1, "2\n", 22
)                      = 2
write(1, "3\n", 23
)                      = 2
write(1, "4\n", 24
)                      = 2
write(1, "5\n", 25
)                      = 2
write(1, "6\n", 26
)                      = 2
write(1, "7\n", 27
)                      = 2
write(1, "8\n", 28
)                      = 2
write(1, "9\n", 29
)                      = 2
write(1, "10\n", 310
)                     = 3
write(1, "n:10\n", 5n:10
)                   = 5
read(255, "", 96)

如何證明head在不同階段結束呢? 簡單加個sleep 3在n:10之前是最明顯的了, head有機會還沒等到sleep 3就先結束整個程序了
也可以把n:10改成第二個for輸出也很明顯 , 執行./a1.sh |strace head 也只會看到第一個for的write 輸出動作 ,所以能研判第二個輸出己不在strace head裡的的程序上了,可能head跑了第二支function接輸入
所以在不同cpu和系統速度也是會有些微差距, 應該說是bash pipe redirect緩慢還是head tail結束太快 , 可能沒有一個標準答案
因為bash本來就不保證結果的順序性, 除非bash開始提供類似交握或protocol.... 以上只是個初步簡易的測試,有錯誤的話還請提點
« 上次編輯: 2016-05-20 07:00 由 rainday »
<0  =_=  Don't learn to hack , hack to learn.

netman

  • 管理員
  • 俺是博士!
  • *****
  • 文章數: 17464
    • 檢視個人資料
    • http://www.study-area.org
Re: linux管道的执行顺序
« 回覆 #11 於: 2016-05-20 15:56 »
rainday大的說明真是精闢!

我的 sample 中用 >&2 的目的就是不要交給 head 來處理, 也就是 stderr 直接輸出到螢幕去。
如此可以驗證前面的輸出是否在後面命令結束時也跟著結束了。
至於 stderr 跟第二個命令輸出順序的確如大大說的那樣: 處理速度問題, 其實也是資源競爭的結果。

rainday

  • 鑽研的研究生
  • *****
  • 文章數: 738
  • 性別: 男
  • enhancing and optimizing
    • 檢視個人資料
Re: linux管道的执行顺序
« 回覆 #12 於: 2016-05-21 17:34 »
呵呵,好說好說,原來netman大還有如此用意
一開始看到這問題討論就回想到以前組合語言的keyboard沾黏判斷
以前工具認識的不多,沒深入了解這部份,現在知道比較多了,剛好就稍微探查一下
<0  =_=  Don't learn to hack , hack to learn.

dark

  • 俺是博士!
  • *****
  • 文章數: 1581
    • 檢視個人資料
Re: linux管道的执行顺序
« 回覆 #13 於: 2016-05-22 23:17 »
原來如此 ...
現在還存在資源競爭問題
因為標準輸出不像 file 會 lock

使用 netman 大例子 , 導向文件

>/tmp/ccc ; (( n=0;for ((i=1;i<=10;i++)); do echo $i; ((n++)); done ; echo n:$n >>/tmp/ccc) | head -n 9 ) >> /tmp/ccc ; cat /tmp/ccc

n:10 不是在頭 , 就是在尾 , 沒有在中間的
合理 ...



既然是資源競爭問題
小弟測試一向在 vmware , 與網大一樣會出現在中間

特地進入 host 執行
( n=0;for ((i=1;i<=10;i++)); do echo $i; ((n++)); done ; echo n:$n >&2 ) | head -n 9
n:10 全部在第一行
執行下個命令 , 比回傳err給父程序快搶得資源 == 也合理

實驗結論 ..
誠徵兩台像好康報報的 HP ...
當然是給網大測試用 ... 小弟只是沾光 ...

我們不會拿來打電玩 ... 真的