linux - Bash script 'while read' loop causes 'broken pipe' error when run with GNU Parallel

08
2014-07
  • Joe White

    According to the GNU Parallel mailing list this is not a GNU Parallel-specific problem. They suggested that I post my problem here.

    The error I'm getting is a "broken pipe" error, but I feel I should first explain the context of my problem and what causes this error. It happens when trying to use any bash script containing a 'while read' loop in GNU Parallel.

    I have a basic bash script like this:

    #!/bin/bash
    # linkcheck.sh
    
    while read domain
    do
    host "$domain"
    done
    

    Assume that I want to pipe in a large list (250mb say).

    cat urllist | ./linkcheck.sh
    

    Running host command on 250mb worth of URLs is rather slow. To speed things up I want to break up the input into chunks before piping it and then run multiple jobs in parallel. GNU Parallel is capable of doing this.

    cat urllist | parallel --pipe -j0 parallel ./linkcheck.sh {}
    

    {} is substituted by the contents of urllist line-by-line. Assume that my systems default setup is capable of running 500ish jobs per instance of parallel. To get round this limitation we can parallelize Parallel itself:

    cat urllist | parallel -j10 --pipe parallel -j0 ./linkcheck.sh {}
    

    This will run 5000'ish jobs. It will also, sadly, cause the error "broken pipe" (bash FAQ). Yet the script starts to work if I remove the while read loop and take input directly from whatever is fed into {} e.g.,

    #!/bin/bash
    # linkchecker.sh
    
    domain="$1"
    host "$1"
    

    Why will it not work with a while read loop? Is it safe to just turn off the SIGPIPE signal to stop the "broken pipe" message, or will that have side effects such as data corruption?

    Thanks for reading.

  • Answers
  • Scott

    So, did

    cat urllist | parallel --pipe -j0 parallel ./linkcheck.sh {}
    

    work correctly?  I believe part of your problem may be that you left out the second --pipe, as in

    cat urllist | parallel -j10 --pipe parallel -j0 --pipe ./linkcheck.sh {}
    

     


    BTW, you never need to say

    cat one_file | some_command
    

    You can always change this to

    some_command < one_file
    

    resulting in one fewer process (and one fewer pipe).  (It may be appropriate/necessary to use cat when you have multiple input files.)

  • Nicole Hamilton

    It appears to me that error may be arising because of a bad race condition because of the window between forking a child to run another copy of linkcheck.sh while the pipe is still open and when the child actually tries to read. In that window, another copy has read EOF and the pipe has closed.


  • Related Question

    linux - bash script while loop if variable is true
  • firebird

    I have the following bash script:

    while [ $loop == "true" ]
       do
         //do stuff
       done
    

    but it says error at [.

    Also this runs as a daemon, when the stop argument is passed to the script...the loop should. I'm guessing setting $loop to false will automatically end the loop.


  • Related Answers
  • Mattias Ahnberg

    The best way to type this would be:

    while [ "$loop" = "true" ]
    

    Read the section "Working out the test-command" at the following URL:
    http://wiki.bash-hackers.org/syntax/quoting

  • JdeBP

    M. Vazquez-Abrams is quite right. This is nothing to do with quotation marks making things that are already strings into strings, or some wrongheaded idea that = in bash's built-in [ command is anything other than a string comparison. (Read § 6.4 of the Bash User Manual, people!) It's everything to do with what happens to empty fields after field-splitting turns words into fields.

    If the shell variable loop is empty or null, then $loop expands to an empty field. After field splitting, empty fields are discarded. Note that field splitting and the check for empty fields precedes quote removal. So "$loop" expands to the field "", which is not empty and is thus not removed. After quote removal it is then an empty field, that becomes an empty argument to the command.

    The [ command requires its = operator to have two operands, fore and aft. Anything else is a syntax error. Since an empty field is removed, the sequence of words

    [ $loop = true ]
    expands to four fields

    1. [
    2. =
    3. true
    4. ]

    when the [ command needs five to be syntactically correct:

    1. [
    2.  
    3. =
    4. true
    5. ]

    Of course, the empty string is not equal to the four-character string true, and the command's exit status is non-zero.

    Again, all of this is in the Bash User Manual, in §3.5 and §3.5.7. The manual is your friend.