Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

I'm trying to write a simple bash function that takes, as its arguments, a number of files and/or directories. It should:

  1. Fully qualify the filenames.
  2. Sort them.
  3. Remove duplicates.
  4. Print all that actually exist.
  5. Return the number of non-existent files.

I have a script that almost does what I want, but falls down on the sorting. The return value of the script as it stands is correct, but the output is not (unsorted and duplicates). If I uncomment the | sort -u statement as indicated, the output is correct but the return value is always 0.

N.B. Simpler solutions to solve the problem are welcome but the question is really about why is this occurring in the code I have. That is, why does adding the pipe seemingly stop the script incrementing the variable r?

Here's the script:

function uniqfile
{
    local r=0 

    for arg in "$@"
    do  
        readlink -e "$arg" || (( ++r ))

    done #| sort -u    ## remove that comment

    return $r
}
share|improve this question
    
Just a small observation. You can reduce for arg in "$@" to for arg. "If 'in WORDS ...;' is not present, then 'in "$@"' is assumed." - help for –  manatwork Sep 30 '11 at 9:35
    
@manatwork Thanks. That's good to know. –  tjm Sep 30 '11 at 9:58

2 Answers 2

up vote 13 down vote accepted

This is a well known bash pitfall, due to this feature:

Each command in a pipeline is executed as a separate process (i.e., in a subshell).

so that modified variables are local to the subshell, and not visible once back in the parent.

To avoid that, rephrase your code to avoid the pipeline, with a process substitution:

 for arg in "$@"
    do  
        readlink -e "$arg" || (( ++r ))

    done > >(sort -u)
share|improve this answer
    
Thankyou. That's great. I wonder if you could tell me the name of the >(..command..) construct. I think I know how it works but feel I should do some further reading. –  tjm Sep 30 '11 at 9:07
2  
@tjm: it is called process substitution –  enzotib Sep 30 '11 at 9:15
    
Process substitution in Bash has many forms: tldp.org/LDP/abs/html/process-sub.html –  slm Sep 15 at 11:58

The | sort -u forces the preceding bit (so the whole for loop) to run in a sub-process (bash needs a 'STDOUT' to redirect into the sort 'STDIN'. (Internet seems to think ksh and bash handle this case slightly differently .. first or last command in the pipe sequence gets put into a subshell?)

This thread goes over a similar problem, and has a neat solution at the end: http://ubuntuforums.org/showthread.php?t=312017

excerpt
    #!/bin/bash
    exec 3< <(du | sort -n)  

    n=0
    while read size dir; do
      [ $size -gt 1000 ] && ((n++))
    done <&3
    exec 3<&-

    echo "Found $n too big files"
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.