IOError: [Errno 32] ท่อแตก: Python


91

ฉันมีสคริปต์ Python 3 ที่เรียบง่ายมาก:

f1 = open('a.txt', 'r')
print(f1.readlines())
f2 = open('b.txt', 'r')
print(f2.readlines())
f3 = open('c.txt', 'r')
print(f3.readlines())
f4 = open('d.txt', 'r')
print(f4.readlines())
f1.close()
f2.close()
f3.close()
f4.close()

แต่มักจะพูดว่า:

IOError: [Errno 32] Broken pipe

ฉันเห็นวิธีที่ซับซ้อนทั้งหมดในอินเทอร์เน็ตในการแก้ไขปัญหานี้ แต่ฉันคัดลอกโค้ดนี้โดยตรงดังนั้นฉันจึงคิดว่ามีบางอย่างผิดปกติกับโค้ดไม่ใช่ SIGPIPE ของ Python

ฉันกำลังเปลี่ยนเส้นทางเอาต์พุตดังนั้นหากสคริปต์ด้านบนมีชื่อว่า "open.py" คำสั่งของฉันที่จะเรียกใช้จะเป็น:

open.py | othercommand

@squiguy line 2:print(f1.readlines())
JOHANNES_NYÅTT

2
คุณได้มีสองการดำเนินงาน IO ที่เกิดขึ้นในบรรทัดที่ 2: การอ่านจากและเขียนไปยังa.txt stdoutบางทีลองแยกสิ่งเหล่านั้นออกเป็นบรรทัดแยกกันเพื่อให้คุณเห็นว่าการดำเนินการใดที่ทำให้เกิดข้อยกเว้น หากstdoutเป็นไปป์และปลายการอ่านถูกปิดนั่นอาจเป็นสาเหตุของEPIPEข้อผิดพลาด
James Henstridge

1
ฉันสามารถสร้างข้อผิดพลาดนี้ซ้ำบนเอาต์พุต (ตามเงื่อนไขที่ถูกต้อง) ดังนั้นฉันจึงสงสัยว่าการprintโทรเป็นผู้ร้าย @ JOHANNES_NYÅTTคุณช่วยอธิบายได้ไหมว่าคุณเปิดใช้สคริปต์ Python ของคุณอย่างไร คุณกำลังเปลี่ยนเส้นทางเอาต์พุตมาตรฐานที่ไหนสักแห่งหรือไม่?
Blckknght

2
คำถามนี้อาจซ้ำกันได้: stackoverflow.com/questions/11423225/…

คำตอบ:


48

ฉันไม่ได้สร้างปัญหาซ้ำ แต่บางทีวิธีนี้อาจช่วยแก้ปัญหาได้: (เขียนทีละบรรทัดstdoutแทนที่จะใช้print)

import sys
with open('a.txt', 'r') as f1:
    for line in f1:
        sys.stdout.write(line)

คุณสามารถจับท่อแตก? สิ่งนี้เขียนไฟล์ไปทีstdoutละบรรทัดจนกว่าท่อจะปิด

import sys, errno
try:
    with open('a.txt', 'r') as f1:
        for line in f1:
            sys.stdout.write(line)
except IOError as e:
    if e.errno == errno.EPIPE:
        # Handle error

คุณต้องตรวจสอบให้แน่ใจว่าothercommandกำลังอ่านจากท่อก่อนที่จะใหญ่เกินไป - /unix/11946/how-big-is-the-pipe-buffer


7
แม้ว่านี่จะเป็นการฝึกฝนการเขียนโปรแกรมที่ดี แต่ฉันไม่คิดว่ามันมีส่วนเกี่ยวข้องกับข้อผิดพลาดของท่อแตกที่ผู้ถามได้รับ (ซึ่งอาจเกี่ยวข้องกับการprintโทรไม่ใช่การอ่านไฟล์)
Blckknght

@Blckknght ฉันได้เพิ่มคำถามและวิธีการทางเลือกสองสามข้อและหวังว่าจะได้รับคำติชมจากผู้เขียน หากปัญหาคือการส่งข้อมูลจำนวนมากจากไฟล์ที่เปิดไปยังคำสั่งการพิมพ์โดยตรงอาจเป็นไปได้ว่าทางเลือกใดทางหนึ่งข้างต้นสามารถแก้ไขได้
Alex L

(วิธีแก้ปัญหาที่ง่ายที่สุดมักจะดีที่สุด - เว้นแต่จะมีเหตุผลเฉพาะในการโหลดไฟล์ทั้งหมดแล้วพิมพ์ออกมาให้ทำวิธีอื่น)
Alex L

1
ทำได้ดีมากในการแก้ไขปัญหานี้! ในขณะที่ฉันสามารถยอมรับคำตอบนี้ได้ แต่ฉันจะรู้สึกซาบซึ้งในสิ่งนี้ก็ต่อเมื่อได้เห็นว่าคำตอบอื่น ๆ (และแนวทางของฉันเอง) เปรียบเทียบกับคำตอบของคุณแล้ว
Jesvin Jose

117

ปัญหาเกิดจากการจัดการ SIGPIPE คุณสามารถแก้ปัญหานี้ได้โดยใช้รหัสต่อไปนี้:

from signal import signal, SIGPIPE, SIG_DFL
signal(SIGPIPE,SIG_DFL) 

ดูความเป็นมาของโซลูชันนี้ได้ที่นี่ คำตอบที่ดีที่นี่


14
สิ่งนี้อันตรายมากอย่างที่ฉันเพิ่งค้นพบเพราะถ้าคุณเคยได้รับ SIGPIPE บนซ็อกเก็ต (httplib หรืออะไรก็ได้) โปรแกรมของคุณจะออกโดยไม่มีการเตือนหรือข้อผิดพลาด
David Bennett

1
@DavidBennett ฉันแน่ใจว่าแอปพลิเคชันขึ้นอยู่กับจุดประสงค์ของคุณคำตอบที่ยอมรับคือคำตอบที่ถูกต้อง มีคำถามและคำตอบอย่างละเอียดมากมายที่นี่เพื่อให้ผู้ใช้สามารถตัดสินใจได้อย่างมีข้อมูล IMO สำหรับเครื่องมือบรรทัดคำสั่งควรละเว้นสัญญาณไปป์ในกรณีส่วนใหญ่
akhan

1
มีวิธีใดบ้างที่ทำได้เพียงชั่วคราว
Nate Glenn

2
@NateGlenn คุณสามารถบันทึกตัวจัดการที่มีอยู่และเรียกคืนได้ในภายหลัง
akhan

3
มีใครช่วยตอบฉันได้ไหมว่าทำไมผู้คนถึงพิจารณาบทความในบล็อกสปอตว่าเป็นแหล่งความจริงที่ดีกว่าเอกสารอย่างเป็นทางการ (คำแนะนำ: เปิดลิงก์เพื่อดูวิธีแก้ไขข้อผิดพลาดของท่อแตกอย่างถูกต้อง) :)
Yurii Rabeshko

93

เพื่อนำคำตอบของอเล็กซ์แอลของที่เป็นประโยชน์ , akhan ของคำตอบที่เป็นประโยชน์และคำตอบที่เป็นประโยชน์ Blckknght ของพร้อมกับข้อมูลเพิ่มเติม:

  • สัญญาณ Standard UnixSIGPIPEจะถูกส่งไปยังกระบวนการที่เขียนไปยังไพพ์เมื่อไม่มีกระบวนการอ่านจากท่อ (อีกต่อไป)

    • นี่ไม่จำเป็นต้องเป็นเงื่อนไขข้อผิดพลาด ยูทิลิตี้ Unix บางอย่างเช่นhead การออกแบบหยุดอ่านก่อนเวลาอันควรจากท่อเมื่อได้รับข้อมูลเพียงพอ
  • โดยค่าเริ่มต้น - กล่าวคือถ้ากระบวนการเขียนไม่ได้ดักจับ อย่างชัดเจนSIGPIPEกระบวนการเขียนก็จะสิ้นสุดลงและรหัสทางออกของมันถูกตั้งค่าเป็น141ซึ่งคำนวณเป็น128(เพื่อยุติสัญญาณโดยสัญญาณโดยทั่วไป) + 13( หมายเลขSIGPIPEสัญญาณเฉพาะของ) .

  • โดยการออกแบบ แต่งูหลามตัวเองกับดักSIGPIPEและแปลให้มันกลายเป็นงูใหญ่IOErrorเช่นมีerrnoค่าerrno.EPIPEเพื่อให้สคริปต์ Python สามารถจับมันถ้าต้องการ - ดูคำตอบของอเล็กซ์แอลของสำหรับวิธีการที่จะทำอย่างนั้น

  • หากสคริปต์ Python ไม่สามารถตรวจจับได้ Python จะแสดงข้อความแสดงข้อผิดพลาดIOError: [Errno 32] Broken pipeและยกเลิกสคริปต์ด้วยรหัสออก1 - นี่คืออาการที่ OP เห็น

  • ในหลาย ๆ กรณีสิ่งนี้ก่อกวนมากกว่าที่เป็นประโยชน์ดังนั้นการเปลี่ยนกลับเป็นพฤติกรรมเริ่มต้นจึงเป็นที่พึงปรารถนา :

    • การใช้signalโมดูลอนุญาตให้เพียงว่าตามที่ระบุไว้ในคำตอบของ akhan ; signal.signal()รับสัญญาณเพื่อจัดการเป็นอาร์กิวเมนต์ที่ 1 และตัวจัดการเป็นตัวที่ 2 ค่าตัวจัดการพิเศษSIG_DFLแสดงถึงพฤติกรรมเริ่มต้นของระบบ:

      from signal import signal, SIGPIPE, SIG_DFL
      signal(SIGPIPE, SIG_DFL) 
      

32

ข้อผิดพลาด "ท่อแตก" เกิดขึ้นเมื่อคุณพยายามเขียนไปยังท่อที่ปิดอยู่อีกด้านหนึ่ง เนื่องจากโค้ดที่คุณแสดงไม่เกี่ยวข้องกับท่อใด ๆ โดยตรงฉันจึงสงสัยว่าคุณกำลังทำอะไรบางอย่างนอก Python เพื่อเปลี่ยนเส้นทางเอาต์พุตมาตรฐานของตัวแปล Python ไปที่อื่น สิ่งนี้อาจเกิดขึ้นได้หากคุณกำลังเรียกใช้สคริปต์เช่นนี้:

python foo.py | someothercommand

ปัญหาที่คุณมีคือการsomeothercommandออกโดยไม่ได้อ่านข้อมูลทั้งหมดที่มีอยู่ในอินพุตมาตรฐาน สิ่งนี้ทำให้การเขียน (ผ่านprint) ของคุณล้มเหลวในบางจุด

ฉันสามารถสร้างข้อผิดพลาดซ้ำด้วยคำสั่งต่อไปนี้บนระบบ Linux:

python -c 'for i in range(1000): print i' | less

ถ้าฉันปิดlessเพจเจอร์โดยไม่เลื่อนผ่านอินพุตทั้งหมด (1,000 บรรทัด) Python จะออกพร้อมกับที่IOErrorคุณรายงาน


10
ใช่นี่เป็นเรื่องจริง แต่ฉันจะแก้ไขได้อย่างไร
JOHANNES_NYÅTT

2
โปรดแจ้งให้เราทราบวิธีแก้ไข
JOHANNES_NYÅTT

1
@ JOHANNES_NYÅTT: อาจใช้งานได้กับไฟล์ขนาดเล็กเนื่องจากมีการบัฟเฟอร์โดยไปป์ในระบบที่คล้ายกับ Unix หากคุณสามารถเขียนเนื้อหาทั้งหมดของไฟล์ลงในบัฟเฟอร์ได้จะไม่ทำให้เกิดข้อผิดพลาดหากโปรแกรมอื่นไม่อ่านข้อมูลนั้น อย่างไรก็ตามหากบล็อกการเขียน (เนื่องจากบัฟเฟอร์เต็ม) ก็จะล้มเหลวเมื่อโปรแกรมอื่นหยุดทำงาน จะพูดอีกครั้ง: คำสั่งอื่นคืออะไร? เราไม่สามารถช่วยคุณได้อีกต่อไปด้วยโค้ด Python เท่านั้น (เนื่องจากไม่ใช่ส่วนที่ทำผิด)
Blckknght

2
ฉันพบปัญหานี้เมื่อไปที่ส่วนหัว ... ข้อยกเว้นหลังจากเอาต์พุตสิบบรรทัด ค่อนข้างมีเหตุผล แต่ก็ยังคาดไม่ถึง :)
André Laszlo

4
@Blckknght: ข้อมูลที่ดีในทั่วไป แต่อีกครั้ง " แก้ไขนั้นและ 'ส่วนหนึ่งที่ทำผิด ' สิ่งที่: SIGPIPEสัญญาณไม่จำเป็นต้องระบุข้อผิดพลาดเงื่อนไขบางสาธารณูปโภค Unix, สะดุดตาhead, โดยการออกแบบในระหว่างการดำเนินการตามปกติใกล้ท่อ ต้นเมื่อพวกเขาได้อ่านเป็นข้อมูลมากที่สุดเท่าที่พวกเขาจำเป็น.
mklement0

21

ฉันรู้สึกว่าจำเป็นต้องชี้ให้เห็นว่าวิธีการใช้

signal(SIGPIPE, SIG_DFL) 

เป็นสิ่งที่อันตรายอย่างแท้จริง(ตามที่แนะนำโดย David Bennet ในความคิดเห็น) และในกรณีของฉันนำไปสู่ธุรกิจตลกที่ขึ้นอยู่กับแพลตฟอร์มเมื่อรวมกับmultiprocessing.Manager(เนื่องจากไลบรารีมาตรฐานอาศัย BrokenPipeError ที่เพิ่มขึ้นในหลาย ๆ ที่) เพื่อให้เรื่องราวสั้น ๆ ที่ยาวนานและเจ็บปวดนี่คือวิธีที่ฉันแก้ไข:

ขั้นแรกคุณต้องจับIOError(Python 2) หรือBrokenPipeError(Python 3) ขึ้นอยู่กับโปรแกรมของคุณคุณสามารถลองออกก่อนเวลานั้นหรือเพิกเฉยต่อข้อยกเว้น

from errno import EPIPE

try:
    broken_pipe_exception = BrokenPipeError
except NameError:  # Python 2
    broken_pipe_exception = IOError

try:
    YOUR CODE GOES HERE
except broken_pipe_exception as exc:
    if broken_pipe_exception == IOError:
        if exc.errno != EPIPE:
            raise

อย่างไรก็ตามนี่ยังไม่เพียงพอ Python 3 อาจยังคงพิมพ์ข้อความเช่นนี้:

Exception ignored in: <_io.TextIOWrapper name='<stdout>' mode='w' encoding='UTF-8'>
BrokenPipeError: [Errno 32] Broken pipe

น่าเสียดายที่การกำจัดข้อความนั้นไม่ตรงไปตรงมา แต่ในที่สุดฉันก็พบhttp://bugs.python.org/issue11380โดยที่ Robert Collins แนะนำวิธีแก้ปัญหานี้ที่ฉันกลายเป็นมัณฑนากรที่คุณสามารถรวมฟังก์ชันหลักของคุณได้ (ใช่นั่นบ้าไปแล้ว การเยื้อง):

from functools import wraps
from sys import exit, stderr, stdout
from traceback import print_exc


def suppress_broken_pipe_msg(f):
    @wraps(f)
    def wrapper(*args, **kwargs):
        try:
            return f(*args, **kwargs)
        except SystemExit:
            raise
        except:
            print_exc()
            exit(1)
        finally:
            try:
                stdout.flush()
            finally:
                try:
                    stdout.close()
                finally:
                    try:
                        stderr.flush()
                    finally:
                        stderr.close()
    return wrapper


@suppress_broken_pipe_msg
def main():
    YOUR CODE GOES HERE

2
สิ่งนี้ดูเหมือนจะไม่สามารถแก้ไขได้สำหรับฉัน
Kyle Bridenstine

มันใช้งานได้สำหรับฉันหลังจากที่ฉันเพิ่มยกเว้น BrokenPipeError: ส่งผ่านในฟังก์ชัน
supress_broken_pipe_msg

2

ฉันรู้ว่านี่ไม่ใช่วิธีที่ "ถูกต้อง" แต่ถ้าคุณสนใจเพียงแค่การกำจัดข้อความแสดงข้อผิดพลาดคุณสามารถลองวิธีแก้ปัญหานี้:

python your_python_code.py 2> /dev/null | other_command

2

คำตอบด้านบน ( if e.errno == errno.EPIPE:) ที่นี่ไม่ได้ผลสำหรับฉันจริงๆ ฉันได้:

AttributeError: 'BrokenPipeError' object has no attribute 'EPIPE'

อย่างไรก็ตามสิ่งนี้ควรได้ผลหากสิ่งที่คุณสนใจคือการเพิกเฉยต่อท่อแตกในการเขียนที่เฉพาะเจาะจง ฉันคิดว่าปลอดภัยกว่าการดัก SIGPIPE:

try:
    # writing, flushing, whatever goes here
except BrokenPipeError:
    exit( 0 )

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


1

นอกจากนี้ยังสามารถเกิดขึ้นได้หากการสิ้นสุดการอ่านเอาต์พุตจากสคริปต์ของคุณตายก่อนกำหนด

คือ open.py | อื่น ๆ

ถ้า otherCommand ออกและ open.py พยายามเขียนไปที่ stdout

ฉันมีบทพูดที่ไม่ดีที่ทำให้ฉันน่ารักขนาดนี้


2
มันไม่ได้เกี่ยวกับขั้นตอนการอ่านจากท่อตายจำเป็นบางสาธารณูปโภค Unix, สะดุดตาhead, โดยการออกแบบในระหว่างการดำเนินการตามปกติใกล้ท่อต้นเมื่อพวกเขาได้อ่านเป็นข้อมูลมากที่สุดเท่าที่พวกเขาจำเป็นต้องใช้ CLI ส่วนใหญ่จะคล้อยตามระบบสำหรับลักษณะการทำงานเริ่มต้น: การยุติกระบวนการอ่านและการรายงานโค้ดออกอย่างเงียบ ๆ141(ซึ่งในเชลล์ไม่ปรากฏให้เห็นได้ทันทีเนื่องจากคำสั่งสุดท้ายของไปป์ไลน์เป็นตัวกำหนดรหัสทางออกโดยรวม) งูหลามพฤติกรรมเริ่มต้นของโชคไม่ดีที่จะตายดัง
mklement0

-2

การปิดควรทำตามลำดับย้อนกลับของการเปิด


4
แม้ว่าจะเป็นการปฏิบัติที่ดีโดยทั่วไปการไม่ทำไม่ได้เป็นปัญหาในตัวเองและไม่ได้อธิบายถึงอาการของ OP
mklement0
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.