วิธีการ "รวม" บรรทัดที่พิมพ์โดยหลาย ๆ โปรแกรมอย่างปลอดภัย?


11

สมมติว่าฉันต้องการรันหลาย ๆ โปรแกรมในแบบคู่ขนานและรวมเอาท์พุทของมันเข้ากับไพพ์เดียว:

sh -c '
    (echo qqq; echo qqq2; echo qqq3)&
    (echo www; echo www2; echo www3)& 
    (echo eee; echo eee2; echo eee3)& 
  wait; wait; wait'

วิธีเชลล์นี้ใช้งานได้ดีสำหรับกรณีง่าย ๆ นี้ แต่ฉันคาดหวังว่ามันจะล้มเหลวหากโปรแกรมส่งออกบรรทัดมากขึ้นและนานขึ้นในลักษณะบัฟเฟอร์เช่นนี้ (สร้างขึ้น):

qqq
qqwww
q2
qqq3www2

wwweee3

eee2
eee3

หนึ่งในวิธีแก้ปัญหาที่ฉันบอกใบ้ให้ใช้คือtail -f:

tail -n +0 -q -f <(echo qqq; echo qqq2; echo qqq3) <(echo www; echo www2; echo www3) <(echo eee; echo eee2; echo eee3)

แต่นี่เป็นตัวเลือกที่ดีที่สุดย่อย: มันแสดงผลข้อมูลซบเซามันไม่ยุติ ฉันเห็นผลลัพธ์ไม่ได้อยู่ในคำสั่ง "sleep" แต่อยู่ในลำดับอาร์กิวเมนต์ในกรณีนี้:

tail -n +0 -q -f <(sleep 1; echo qqq; sleep 1; echo qqq2; echo qqq3) <(echo www; echo www2; sleep 10; echo www3) <(echo eee; sleep 4; echo eee2; echo eee3) | cat

ฉันได้ใช้โปรแกรมพิเศษขนาดเล็กสำหรับสิ่งนี้ แต่เชื่อว่าควรมีวิธีที่ดีในการทำเช่นนั้น

วิธีการใช้เครื่องมือมาตรฐาน (และไม่tail -fเสียเปรียบ)?


คุณต้องการผสมเอาต์พุตอย่างไร เห็นได้ชัดว่าคุณต้องการผสมเอาท์พุทเนื่องจากคุณต้องการ "คำสั่งการนอนหลับ" มากกว่า "คำสั่งอาร์กิวเมนต์" ความต้องการของคุณในการผสมเอาท์พุท แต่ไม่ใช่บรรทัดนั่นคือให้พิมพ์แต่ละบรรทัดตามอะตอมหรือไม่
Gilles 'หยุดความชั่วร้าย'

Linewise บรรทัดทั้งหมดจากโปรแกรมที่เริ่มต้นทั้งหมดควรส่งก่อน แต่ไม่มีการผสมในแต่ละบรรทัด
วิ

ฉันคิดว่าวิธีการมาตรฐานในการทำสิ่งนี้เรียกว่าดีsyslog...
Shadur

การใช้syslogไม่ใช่สำหรับบันทึก แต่สำหรับบางสิ่งบางอย่างจะถือว่าใช้ได้หรือไม่
วิ

สิ่งนี้ไม่เหมาะอย่างยิ่งไปกว่าคำแนะนำอื่น ๆ ที่โพสต์ไปแล้ว แต่ฉันคิดว่ามันคุ้มค่าที่จะพูดถึง-sตัวเลือกสำหรับหาง เช่นtail -f -s .1 fileจะลดการหน่วงลูปเป็น. 1 วินาทีจากค่าเริ่มต้น 1 วินาที
cpugeniusmv

คำตอบ:


4

GNU Parallel

จากบันทึกประจำรุ่นลงวันที่สิงหาคม 2556:

--line-bufferจะบัฟเฟอร์เอาต์พุตบนพื้นฐานบรรทัด --groupเก็บเอาท์พุทไว้ด้วยกันสำหรับงานทั้งหมด --ungroupอนุญาตให้เอาต์พุตรวมกันกับครึ่งบรรทัดที่มาจากงานหนึ่งและครึ่งบรรทัดมาจากงานอื่น --line-bufferเหมาะกับระหว่างสองคนนี้; มันพิมพ์เต็มบรรทัด แต่จะช่วยให้การผสมของงานที่แตกต่างกัน

ตัวอย่างเช่น:

parallel --line-buffer <jobs

ที่jobsประกอบด้วย:

./long.sh
./short.sh one
./short.sh two

short.sh:

#!/bin/bash

while true; do
        echo "short line $1"
        sleep .1
done

long.sh:

#!/bin/bash

count=0
while true; do
        echo -n "long line with multiple write()s "
        sleep .1
        count=$((count+1))
        if [ $count -gt 30 ]; then
                count=0
                echo
        fi
done

เอาท์พุท:

short line one
short line two
short line one
short line two
short line one
**-snip-**
short line one
short line one
short line two
short line two
short line one
short line one
short line one
long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s 
short line two
short line two
short line two
short line one

1

โซลูชันที่ใช้ล็อก:

function putlines () {
   read line || return $?
   while ! ln -s $$ lock >/dev/null 2>&1
   do
      sleep 0.05
   done
   echo "$line" 
}

function getlines () {
     while read lline
     do 
          echo "$lline"
          rm lock
     done
}

# your paralelized jobs  
(  
   job1 | putlines & 
   job2 | putlines & 
   job3 | putlines & 
   wait
) | getlines| final_processing

ควรมีวิธีที่เร็วกว่าในการสร้างการล็อกมากกว่าการใช้ระบบไฟล์


0

ฉันไม่สามารถนึกถึงอะไรง่ายๆที่จะช่วยคุณถ้าบรรทัดของคุณยาวมากโปรแกรมหนึ่งจะถูกส่งไปยังโหมดสลีปก่อนที่จะสามารถเขียนบรรทัดเพื่อ stdout ได้

อย่างไรก็ตามหากบรรทัดของคุณสั้นพอที่จะเขียนทั้งหมดก่อนที่จะเปลี่ยนกระบวนการและปัญหาของคุณคือการสร้างหนึ่งบรรทัดใช้เวลานานมากคุณสามารถบัฟเฟอร์เอาต์พุตโดยใช้การอ่าน

เช่น:

((./script1 | while read line1; do echo $line1; done) & \
(./script2 | while read line2; do echo $line2; done)) | doSomethingWithOutput

ไม่สวย. ไม่น่าเชื่อว่า ไม่น่าที่ประสิทธิภาพจะดี
วิ

คุณถูก. มันไม่สวย แต่ดูเหมือนแฮ็คสกปรก อย่างไรก็ตามฉันไม่คิดว่าเพียงพอที่จะตัดสินประสิทธิภาพและความน่าเชื่อถือ นอกจากนี้คุณต้องการใช้ 'เครื่องมือมาตรฐาน' ดังนั้นฉันจะไม่แปลกใจถ้าคุณต้องยอมรับความอัปลักษณ์ (ในที่สุด) แต่บางคนอาจมีทางออกที่น่าพอใจมากกว่านี้
xwst

ขณะนี้ฉันพอใจกับโปรแกรมของฉัน (ลิงก์ในคำถาม) ยกเว้นว่ามันไม่สามารถใช้ได้ในที่เก็บดังนั้นจึงไม่สามารถพิจารณาได้ว่าแม้แต่ "มาตรฐาน" เพียงเล็กน้อย วิธีแก้ปัญหาอาจจะพยายามผลักไปที่นั่น ...
ไว

0

คุณสามารถสร้างไพmkfifoพ์ที่มีชื่อด้วยดัมพ์เอาต์พุตทั้งหมดลงในไพพ์ที่มีชื่อและอ่านแยกจากไพพ์ที่มีชื่อสำหรับข้อมูลที่รวบรวมของคุณ:

mkfifo /tmp/mypipe
job1 > /tmp/mypipe &
job2 > /tmp/mypipe &
job3 > /tmp/mypipe &

cat /tmp/mypipe > /path/to/final_output &

wait; wait; wait; wait

1
สิ่งนี้จะป้องกันจากการ mangling เมื่อใดjob1และjob2ส่งออกสายยาว (> 4096 ไบต์)? ดูเหมือนว่าจะมีชื่อเทียบเท่าไพพ์ของรหัสตัวอย่างแรกมากใน Quesion
วิ

จุดยุติธรรมมาก ฉันไม่ได้พิจารณาผลลัพธ์ขนาดใหญ่หยดแม้ว่าจะถูกเรียกออกมาอย่างชัดเจนในคำถามของคุณ ตอนนี้ฉันสงสัยว่าอาจไม่มีเครื่องมือบางอย่างที่ย้อนกลับของteeซึ่งดูเหมือนว่าสิ่งที่คุณต้องการ อาจดูที่ internals ของsyslogหรือเครื่องมือการบันทึกอื่น ๆ เพราะพวกเขารวมเอาท์พุทจากหลาย ๆ ที่เป็นไฟล์บันทึกเดียว การล็อกอาจเป็นคำตอบที่ถูกต้องตามที่ @emmanual แนะนำเช่นกัน
DopeGhoti

0

คำถามเก่า ๆ ฉันรู้ แต่ฉันสงสัยเกี่ยวกับสิ่งเดียวกันและนี่คือสิ่งที่ฉันคิดขึ้นมา:

garbling_job | (
    while read LINE
    do
        echo $LINE
    done
) &

ฉันดูเหมือนจะสามารถเริ่มต้นได้ไม่กี่คนโดยไม่ต้องกังวลเกี่ยวกับผลลัพธ์ที่อ่านไม่ออก

นี่คือโปรแกรมทดสอบของฉัน

if [ "$1" = "go" ]
then
for i in 1 2
do
    printf 111112222222222223333
    sleep .01
    printf 3333333444444444444555555555555
    sleep .01
    printf 6666666666666667777
    sleep .01
    printf 777777788888888889999999999999999
    sleep .01
    echo
done
exit
fi

# running them in sequence is all very fine
for i in 1 2 3 4 5 6 7 8
do
    echo bash $0 go 
done

# now this is all garbled up
for i in 1 2 3 4 5 6 7 8
do
    bash $0 go &
done
for i in 1 2 3 4 5 6 7 8; do wait; done

# using cat inbetween does not make it better
for i in 1 2 3 4 5 6 7 8
do
    bash $0 go | cat &
done
for i in 1 2 3 4 5 6 7 8; do wait; done

# it does not help to use stdbuff after the thing that just printfs sporadicall
for i in 1 2 3 4 5 6 7 8
do
    bash $0 go | stdbuf -oL cat &
done
for i in 1 2 3 4 5 6 7 8; do wait; done

# it does not help to use stdbuff before either - or I am not understanding stdbuff
for i in 1 2 3 4 5 6 7 8
do
    stdbuf -o10000 bash $0 go | stdbuf -oL cat &
echo
done
for i in 1 2 3 4 5 6 7 8; do wait; done

# can I read - yes - they are now fine again
for i in 1 2 3 4 5 6 7 8
do
bash $0 go | (
    while read LINE
    do
        echo $LINE
    done
) &
echo
done
for i in 1 2 3 4 5 6 7 8; do wait; done

1
คุณต้องทดสอบด้วยบรรทัดที่ใหญ่กว่าขนาดหน้ากระดาษของคุณ (โดยทั่วไปคือ 4-8K) ดูmywiki.wooledge.org/…
Ole Tange
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.