Làm thế nào tôi có thể thực hiện một luồng dữ liệu tuần hoàn giữa các lệnh được kết nối với nhau?


19

Tôi biết hai loại cách các lệnh có thể được kết nối với nhau:

  1. bằng cách sử dụng một ống (đưa đầu ra std vào đầu vào std của lệnh tiếp theo).
  2. bằng cách sử dụng Tee (ghép đầu ra thành nhiều đầu ra).

Tôi không biết nếu đó là tất cả những gì có thể, vì vậy tôi rút ra một loại kết nối giả định:

nhập mô tả hình ảnh ở đây

Làm thế nào có thể thực hiện một luồng dữ liệu tuần hoàn giữa các lệnh như ví dụ trong mã giả này, nơi tôi sử dụng các biến thay vì các lệnh:

pseudo-code:

a = 1    # start condition 

repeat 
{
b = tripple(a)
c = sin(b) 
a = c + 1 
}

Câu trả lời:


16

Vòng lặp I / O được thực hiện với tail -f

Điều này thực hiện một vòng lặp I / O tròn:

$ echo 1 >file
$ tail -f file | while read n; do echo $((n+1)); sleep 1; done | tee -a file
2
3
4
5
6
7
[..snip...]

Điều này thực hiện vòng lặp đầu vào / đầu ra tròn bằng thuật toán sin mà bạn đã đề cập:

$ echo 1 >file
$ tail -f file | while read n; do echo "1+s(3*$n)" | bc -l; sleep 1; done | tee -a file
1.14112000805986722210
.72194624281527439351
1.82812473159858353270
.28347272185896349481
1.75155632167982146959
[..snip...]

Ở đây, bctoán học dấu phẩy động và s(...)là ký hiệu của bc cho hàm sin.

Thay vào đó, việc thực hiện cùng một thuật toán sử dụng một biến

Đối với ví dụ toán học cụ thể này, không cần đến cách tiếp cận I / O tròn. Một cách đơn giản có thể cập nhật một biến:

$ n=1; while true; do n=$(echo "1+s(3*$n)" | bc -l); echo $n; sleep 1; done
1.14112000805986722210
.72194624281527439351
1.82812473159858353270
.28347272185896349481
[..snip...]

12

Bạn có thể sử dụng một bộ xếp hình cho cái này, được tạo bằng mkfifo. Tuy nhiên, lưu ý rằng rất dễ vô tình tạo ra bế tắc. Hãy để tôi giải thích rằng, hãy lấy ví dụ "thông tư" giả thuyết của bạn. Bạn cung cấp đầu ra của lệnh cho đầu vào của lệnh. Có ít nhất hai cách điều này có thể bế tắc:

  1. Lệnh có một bộ đệm đầu ra. Nó được lấp đầy một phần, nhưng chưa được xóa (thực sự được viết). Nó sẽ làm như vậy một khi nó lấp đầy. Vì vậy, nó trở lại để đọc đầu vào của nó. Nó sẽ ngồi đó mãi mãi, bởi vì đầu vào mà nó chờ đợi thực sự nằm trong bộ đệm đầu ra. Và nó sẽ không bị xóa cho đến khi nhận được đầu vào đó ...

  2. Lệnh có một loạt các đầu ra để viết. Nó bắt đầu viết nó, nhưng bộ đệm ống nhân lấp đầy. Vì vậy, nó ngồi đó, chờ đợi chúng là không gian trong bộ đệm. Điều đó sẽ xảy ra ngay khi nó đọc đầu vào của nó, tức là, sẽ không bao giờ làm điều đó cho đến khi nó hoàn thành việc viết bất cứ điều gì vào đầu ra của nó.

Điều đó nói rằng, đây là cách bạn làm điều đó. Ví dụ này là với od, để tạo ra một chuỗi hex không bao giờ kết thúc:

mkfifo fifo
( echo "we need enough to make it actually write a line out"; cat fifo ) \ 
    | stdbuf -i0 -o0 -- od -t x1 | tee fifo

Lưu ý rằng cuối cùng dừng lại. Tại sao? Nó bế tắc, số 2 ở trên. Bạn cũng có thể nhận thấy stdbufcuộc gọi trong đó, để tắt bộ đệm. Không có nó? Bế tắc mà không có bất kỳ đầu ra.


cảm ơn, tôi không biết gì về bộ đệm trong ngữ cảnh này, bạn có biết một số từ khóa để đọc thêm về nó không?
Abdul Al Hazred 30/03/2015

1
@AbdulAlHazred Để đệm bộ đệm đầu vào / đầu ra, hãy tìm bộ đệm stdio . Đối với bộ đệm của kernel trong một đường ống, bộ đệm ống dường như hoạt động.
derobert

4

Nói chung, tôi sẽ sử dụng Makefile (lệnh make) và cố gắng ánh xạ sơ đồ của bạn theo quy tắc makefile.

f1 f2 : f0
      command < f0 > f1 2>f2

Để có các lệnh lặp lại / tuần hoàn, chúng ta cần xác định chính sách lặp. Với:

SHELL=/bin/bash

a.out : accumulator
    cat accumulator <(date) > a.out
    cp a.out accumulator

accumulator:
    touch accumulator     #initial value

mỗi cái makesẽ tạo ra một lần lặp tại thời điểm đó.


Lạm dụng dễ thương make, nhưng không cần thiết: Nếu bạn sử dụng một tệp trung gian, tại sao không sử dụng một vòng lặp để quản lý nó?
alexis

@alexis, makefiles có lẽ là quá mức cần thiết. Tôi không thoải mái lắm về các vòng lặp: Tôi bỏ lỡ khái niệm đồng hồ, tình trạng tạm dừng hoặc một ví dụ rõ ràng. Các sơ đồ ban đầu ghi nhớ cho tôi Workflows và chữ ký hàm. Đối với các sơ đồ phức tạp, cuối cùng chúng ta sẽ cần các kết hợp dữ liệu hoặc thực hiện các quy tắc gõ. (đây chỉ là một trực giác lạm dụng)
JJoao 30/03/2015

@alexis, và tất nhiên, tôi đồng ý với bạn.
JJoao 30/03/2015

Tôi không nghĩ đây là lạm dụng - makelà về macro là một ứng dụng hoàn hảo ở đây.
mikeerv

1
@mikeerv, Vâng. Và tất cả chúng ta đều biết rằng các công cụ lạm dụng là Magna Carta ngầm của Unix :)
JJoao

4

Bạn biết đấy, tôi không tin rằng bạn nhất thiết cần một vòng phản hồi lặp đi lặp lại như sơ đồ của bạn, nhiều đến mức bạn có thể sử dụng một đường ống liên tục giữa các bộ xử lý . Sau đó, một lần nữa, có thể không có quá nhiều sự khác biệt - một khi bạn mở một dòng trên bộ đồng xử lý, bạn có thể thực hiện các vòng lặp kiểu điển hình chỉ cần viết thông tin và đọc thông tin từ nó mà không làm bất cứ điều gì khác thường.

Ở nơi đầu tiên, nó sẽ xuất hiện bclà một ứng cử viên chính cho việc đồng xử lý cho bạn. Trong bcbạn có thể các definechức năng có thể làm khá nhiều những gì bạn yêu cầu trong mã giả của bạn. Ví dụ, một số chức năng rất đơn giản để thực hiện việc này có thể trông giống như:

printf '%s()\n' b c a |
3<&0 <&- bc -l <<\IN <&3
a=1; b=0; c=0;
define a(){ "a="; return (a = c+1); }
define b(){ "b="; return (b = 3*a); }
define c(){ "c="; return (c = s(b)); }
IN

... sẽ in ...

b=3
c=.14112000805986722210
a=1.14112000805986722210

Nhưng tất nhiên, nó không kéo dài . Ngay sau khi phần phụ chịu trách nhiệm printfthoát khỏi đường ống (ngay sau khi printfghi a()\nvào đường ống) , đường ống bị phá hủy và bcđầu vào của nó đóng lại và nó cũng thoát ra. Điều đó gần như không hữu ích như nó có thể.

@derobert đã đề cập đến các FIFO như có thể có bằng cách tạo một tệp ống có tên với mkfifotiện ích. Đây thực chất chỉ là các đường ống, ngoại trừ nhân hệ thống liên kết một mục hệ thống tập tin với cả hai đầu. Đây là rất hữu ích, nhưng nó sẽ đẹp hơn nếu bạn có thể có một đường ống mà không có nguy cơ nó bị rình mò trong hệ thống tập tin.

Khi nó xảy ra, vỏ của bạn làm điều này rất nhiều. Nếu bạn sử dụng một lớp vỏ thực hiện thay thế quá trình thì bạn có một phương tiện rất đơn giản để có được một đường ống lâu dài - loại mà bạn có thể gán cho một quá trình nền mà bạn có thể giao tiếp.

Trong bashVí dụ, bạn có thể thấy cách thức hoạt động thay thế tiến trình:

bash -cx ': <(:)'
+ : /dev/fd/63

Bạn thấy nó thực sự là một sự thay thế . Shell thay thế một giá trị trong quá trình mở rộng tương ứng với đường dẫn đến một liên kết đến một đường ống . Bạn có thể tận dụng điều đó - bạn không cần bị hạn chế chỉ sử dụng đường ống đó để liên lạc với bất kỳ quy trình nào chạy trong chính sự ()thay thế ...

bash -c '
    eval "exec 3<>"<(:) "4<>"<(:)
    cat  <&4 >&3  &
    echo hey cat >&4
    read hiback  <&3
    echo "$hiback" here'

... mà in ...

hey cat here

Bây giờ tôi biết rằng các shell khác nhau thực hiện việc đồng xử lý theo các cách khác nhau - và có một cú pháp cụ thể bashđể thiết lập một (cũng có thể là một cho zshcả) - nhưng tôi không biết những thứ đó hoạt động như thế nào. Tôi chỉ biết rằng bạn có thể sử dụng cú pháp trên để thực hiện hầu như cùng một việc mà không cần tất cả sự nghiêm khắc trong cả hai bashzsh- và bạn có thể làm một điều rất giống nhau trong dashbusybox ashđể đạt được cùng một mục đích với các tài liệu ở đây (bởi vì dashbusyboxlàm ở đây- tài liệu với các đường ống chứ không phải các tập tin tạm thời như hai người khác làm) .

Vì vậy, khi áp dụng vào bc...

eval "exec 3<>"<(:) "4<>"<(:)
bc -l <<\INIT <&4 >&3 &
a=1; b=0; c=0;
define a(){ "a="; return (a = c+1); }
define b(){ "b="; return (b = 3*a); }
define c(){ "c="; return (c = s(b)); }
INIT
export BCOUT=3 BCIN=4 BCPID="$!"

... đó là phần khó khăn. Và đây là phần thú vị ...

set --
until [ "$#" -eq 10 ]
do    printf '%s()\n' b c a >&"$BCIN"
      set "$@" "$(head -n 3 <&"$BCOUT")"
done; printf %s\\n "$@"

... mà in ...

b=3
c=.14112000805986722210
a=1.14112000805986722210
#...24 more lines...
b=3.92307618030433853649
c=-.70433330413228041035
a=.29566669586771958965

... và nó vẫn đang chạy ...

echo a >&"$BCIN"
read a <&"$BCOUT"
echo "$a"

... mà chỉ được cho tôi những giá trị cuối cùng cho bc's athay vì gọi các a()chức năng để tăng nó và in ...

.29566669586771958965

Trên thực tế, nó sẽ tiếp tục chạy cho đến khi tôi giết nó và phá vỡ các đường ống IPC của nó ...

kill "$BCPID"; exec 3>&- 4>&-
unset BCPID BCIN BCOUT

1
Rất thú vị. Lưu ý với bash và zsh gần đây, bạn không phải chỉ định bộ mô tả tệp, ví dụ: cũng eval "exec {BCOUT}<>"<(:) "{BCIN}<>"<(:)hoạt động
Thor
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.