Implement subset of shell script
C (340 bytes)

I have no experience at all in golfing, but you have to start somewhere, so here goes:

#define W m||(*t++=p,m=1);
#define C(x) continue;case x:if(m&amp;2)break;
c;m;f[2];i;char b[512],*p=b,*a[16],**t=a;main(){f[1]=1;while(~(c=getchar())){
switch(c){case 39:W m^=3;C('|')if(pipe(f))C(10)if(t-a){*t=*p=0;fork()||(dup2(
i,!dup2(f[1],1)),execvp(*a,a));f[1]-1&amp;&amp;close(f[1]);i=*f;*f=m=0;f[1]=1;p=b;t=a
;}C(32)m&amp;1?*p++=0,m=0:0;C(0)}W*p++=c;}}


I added line breaks so you won't have to scroll, but didn't include them in my count since they are without semantic significance. Those after preprocessor directives are required and were counted.

Ungolfed version

#define WORDBEGIN   mode || (*thisarg++ = pos, mode = 1);
#define CASE(x)     continue; case x: if (mode &amp; 2) break;

// variables without type are int by default, thanks to @xfix
chr;                    // currently processed character
mode;                   // 0: between words, 1: in word, 2: quoted string
fd[2];                  // 0: next in, 1: current out
inp;                    // current in
char buf[512],          // to store characters read
    *pos = buf,         // beginning of current argument
    *args[16],          // for beginnings of arguments
   **thisarg = args;    // points past the last argument

main() {                          // codegolf.stackexchange.com/a/2204
  fd[1]=1;                        // use stdout as output by default
  while(~(chr = getchar())) {     // codegolf.stackexchange.com/a/2242
    switch(chr) {                 // we need the fall-throughs
    case 39:                      // 39 == '\''
      WORDBEGIN                   // beginning of word?
      mode ^= 3;                  // toggle between 1 and 2
    CASE('|')
      if(pipe(fd))                // create pipe and fall through
    CASE(10)                      // 10 == '\n'
      if (thisarg-args) {         // any words present, execute command
        *thisarg = *pos = 0;      // unclean: pointer from integer
        //for (chr = 0; chr &lt;=  thisarg - args; ++chr)
        //  printf("args[%d] = \"%s\"\n", chr, args[chr]);
        fork() || (
          dup2(inp,!dup2(fd[1],1)),
          execvp(*args, args)
        );
        fd[1]-1 &amp;&amp; close(fd[1]);  // must close to avoid hanging suprocesses
        //inp &amp;&amp; close(inp);      // not as neccessary, would be cleaner
        inp = *fd;                // next in becomes current in
        *fd = mode = 0;           // next in is stdin
        fd[1] = 1;                // current out is stdout
        pos = buf;
        thisarg = args;
      }
    CASE(32)                      // 32 == ' '
      mode &amp; 1  ?                 // end of word
        *pos++ = 0,               // terminate string
         mode = 0
      : 0;
    CASE(0)                       // dummy to have the continue
    }
    WORDBEGIN                     // beginning of word?
    *pos++ = chr;
  }
}


Features


Parallel execution: you can type the next command while the one before is still executing.
Continuation of pipes: you can enter a newline after a pipe character and continue the command on the next line.
Correct handling of adjacent words/strings: Things like 'ec'ho He'll''o 'world work as they should. Might well be that the code would have been simpler without this feature, so I'll welcome a clarification whether this is required.


Known problems


Half the file descriptors are never closed, child processes never reaped. In the long run, this will likely cause some kind of resource exhaustion.
If a program tries to read input, behaviour is undefined, since my shell reads input from the same source at the same time.
Anything might happen if the execvp call fails, e.g. due to a mistyped program name. Then we have two processes playing at being shell simultaneously.
Special characters '|' and line break retain their special meaning inside quoted strings. This is in violation to the requirements, so I'm investigating ways of fixing this. Fixed, at a cost of about 11 bytes.


Other Notes


The thing obviously does not include a single header, so it depends on implicit declarations of all functions used. Depending on calling conventions, this might or might not be a problem.
Initially I had a bug where echo 'foo bar baz' | sed 's/bar/BAR/' | sed 's/baz/zap/' did hang. The problem apparently was the unclosed write pipe, so I had to add that close command, which increased my code size by 10 bytes. Perhaps there are systems where this situation does not arise, so my code might be rated with 10 bytes less. I don't know.
Thanks to the C golfing tips, in particular no return type for main, EOF handling and ternary operator, the last one for pointing out that ?: can have nested , without (…).