diff --git a/Cargo.lock b/Cargo.lock
index 390f3c1b883727a8c71501d4e425355f615b9399..aa81d891b2da92cb235e2899202fddf543ab2c98 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -9,11 +9,11 @@ dependencies = [
  "futures 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)",
  "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)",
- "liner 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "liner 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "nix 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "peg 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "permutate 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "redox_syscall 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)",
+ "redox_syscall 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)",
  "smallstring 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "smallvec 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "tokio-core 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -65,7 +65,7 @@ dependencies = [
 
 [[package]]
 name = "cfg-if"
-version = "0.1.1"
+version = "0.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
@@ -113,7 +113,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "liner"
-version = "0.3.1"
+version = "0.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "termion 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -166,7 +166,7 @@ name = "net2"
 version = "0.2.29"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "cfg-if 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)",
  "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -179,7 +179,7 @@ version = "0.8.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "cfg-if 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)",
  "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
@@ -213,7 +213,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "redox_syscall"
-version = "0.1.19"
+version = "0.1.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
@@ -359,7 +359,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 "checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5"
 "checksum byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c40977b0ee6b9885c9013cd41d9feffdd22deb3bb4dc3a71d901cc7a77de18c8"
 "checksum bytes 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8b24f16593f445422331a5eed46b72f7f171f910fead4f2ea8f17e727e9c5c14"
-"checksum cfg-if 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d0c47d456a36ebf0536a6705c83c1cbbcb9255fbc1d905a6ded104f479268a29"
+"checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de"
 "checksum fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6cc484842f1e2884faf56f529f960cc12ad8c71ce96cc7abba0a067c98fee344"
 "checksum futures 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "4b63a4792d4f8f686defe3b39b92127fea6344de5d38202b2ee5a11bbbf29d6a"
 "checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb"
@@ -367,7 +367,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
 "checksum lazycell 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ce12306c4739d86ee97c23139f3a34ddf0387bbf181bc7929d287025a8c3ef6b"
 "checksum libc 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)" = "38f5c2b18a287cf78b4097db62e20f43cace381dc76ae5c0a3073067f78b7ddc"
-"checksum liner 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b671c4f2d2f7bfb49c4231220d51f14482adf31dca8c936a037b0a7d40b79d04"
+"checksum liner 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1f5c044840b473e0c1a29a05a3b82beeadb688f9cfdaadc31955a5dc90149a39"
 "checksum log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "880f77541efa6e5cc74e76910c9884d9859683118839d6a1dc3b11e63512565b"
 "checksum mio 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)" = "9e965267d4d58496fc4f740e9861118367f13570cadf66316ed2c3f2f14d87c7"
 "checksum mio-uds 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "1731a873077147b626d89cc6c2a0db6288d607496c5d10c0cfcf3adc697ec673"
@@ -378,7 +378,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 "checksum peg 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "36a474cba42744afe0f223e9d4263594b3387f172e512259c72d2011e477c4fb"
 "checksum permutate 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "53b7d5b19a715ffab38693a9dd44b067fdfa2b18eef65bd93562dfe507022fae"
 "checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a"
-"checksum redox_syscall 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)" = "e4a357d14a12e90a37d658725df0e6468504750b5948b9710f83f94a0c5818e8"
+"checksum redox_syscall 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)" = "1eb6b797b89e9c92681e837851e906e9788c748391deaba7f5b66f264e390249"
 "checksum scoped-tls 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f417c22df063e9450888a7561788e9bd46d3bb3c1466435b4eccb903807f147d"
 "checksum shell32-sys 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "72f20b8f3c060374edb8046591ba28f62448c369ccbdc7b02075103fb3a9e38d"
 "checksum slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "17b4fcaed89ab08ef143da37bc52adbcc04d4a69014f4c1208d6b51f0c47bc23"
diff --git a/src/parser/pipelines.rs b/src/parser/pipelines.rs
index 9a42c00b31bb2458c85eae9d68cbd39ccd9ba2d8..4117d71fa33e0909fe4b562c36219bea10d03c5c 100644
--- a/src/parser/pipelines.rs
+++ b/src/parser/pipelines.rs
@@ -188,6 +188,10 @@ pub fn collect(possible_error: &mut Option<&str>, args: &str) -> Pipeline {
                                     let _ = args_iter.next();
                                     redir_found!(RedirMode::Stdout(RedirectFrom::Both));
                                 },
+                                Some(&b'|') => {
+                                    let _ = args_iter.next();
+                                    job_found!(RedirectFrom::Both, true);
+                                },
                                 _ => job_found!(RedirectFrom::Stdout, false)
                             }
                         },
diff --git a/src/shell/pipe.rs b/src/shell/pipe.rs
index cc4e2aa528d5bc1e49f404e9cd4a04f15ddc9935..7f58c8b82b8aa14f4ec7fe334aa407627586f140 100644
--- a/src/shell/pipe.rs
+++ b/src/shell/pipe.rs
@@ -4,7 +4,7 @@
 #[cfg(target_os = "redox")] use syscall;
 use std::io::{self, Write};
 use std::process::{Stdio, Command, Child};
-use std::os::unix::io::{FromRawFd, AsRawFd, IntoRawFd};
+use std::os::unix::io::{FromRawFd, IntoRawFd};
 use std::os::unix::process::CommandExt;
 use std::fs::{File, OpenOptions};
 use std::process::exit;
@@ -13,86 +13,181 @@ use super::{JobKind, Shell};
 use super::status::*;
 use parser::peg::{Pipeline, RedirectFrom};
 
-/// The purpose of the signal handler is to ignore signals when it is active, and then continue
-/// listening to signals once the handler is dropped.
-struct SignalHandler;
+use self::crossplat::*;
+
+/// The `crossplat` module contains components that are meant to be abstracted across
+/// different platforms
+#[cfg(not(target_os = "redox"))]
+mod crossplat {
+    use libc;
+    use nix::{fcntl, unistd};
+    use parser::peg::{RedirectFrom};
+    use std::fs::File;
+    use std::io::Error;
+    use std::os::unix::io::{IntoRawFd, FromRawFd};
+    use std::process::{Stdio, Command};
+
+    /// The purpose of the signal handler is to ignore signals when it is active, and then continue
+    /// listening to signals once the handler is dropped.
+    pub struct SignalHandler;
+
+    impl SignalHandler {
+        pub fn new() -> SignalHandler {
+            unsafe { let _ = libc::signal(libc::SIGTTOU, libc::SIG_IGN); }
+            SignalHandler
+        }
+    }
 
-impl SignalHandler {
-    #[cfg(all(unix, not(target_os = "redox")))]
-    pub fn new() -> SignalHandler {
-        unsafe { let _ = libc::signal(libc::SIGTTOU, libc::SIG_IGN); }
-        SignalHandler
+    impl Drop for SignalHandler {
+        fn drop(&mut self) {
+            unsafe { let _ = libc::signal(libc::SIGTTOU, libc::SIG_DFL); }
+        }
     }
 
-    #[cfg(target_os = "redox")]
-    pub fn new() -> SignalHandler {
-        // TODO
-        SignalHandler
+    pub fn unmask_sigtstp() {
+        unsafe {
+            use libc::{sigset_t, SIG_UNBLOCK, SIGTSTP, sigemptyset, sigaddset, sigprocmask};
+            use std::mem;
+            use std::ptr;
+            let mut sigset = mem::uninitialized::<sigset_t>();
+            sigemptyset(&mut sigset as *mut sigset_t);
+            sigaddset(&mut sigset as *mut sigset_t, SIGTSTP);
+            sigprocmask(SIG_UNBLOCK, &sigset as *const sigset_t, ptr::null_mut() as *mut sigset_t);
+        }
     }
-}
 
-impl Drop for SignalHandler {
-    #[cfg(all(unix, not(target_os = "redox")))]
-    fn drop(&mut self) {
-        unsafe { let _ = libc::signal(libc::SIGTTOU, libc::SIG_DFL); }
+    /// When given a process ID, that process will be assigned to a new process group.
+    pub fn create_process_group() {
+        let _ = unistd::setpgid(0, 0);
     }
 
-    #[cfg(target_os = "redox")]
-    fn drop(&mut self) {
-        // TODO
+    /// When given a process ID, that process's group will be assigned as the foreground process group.
+    pub fn set_foreground(pid: u32) {
+        let _ = unistd::tcsetpgrp(0, pid as i32);
+        let _ = unistd::tcsetpgrp(1, pid as i32);
+        let _ = unistd::tcsetpgrp(2, pid as i32);
     }
-}
 
-#[cfg(all(unix, not(target_os = "redox")))]
-fn unmask_sigtstp() {
-    unsafe {
-        use libc::{sigset_t, SIG_UNBLOCK, SIGTSTP, sigemptyset, sigaddset, sigprocmask};
-        use std::mem;
-        use std::ptr;
-        let mut sigset = mem::uninitialized::<sigset_t>();
-        sigemptyset(&mut sigset as *mut sigset_t);
-        sigaddset(&mut sigset as *mut sigset_t, SIGTSTP);
-        sigprocmask(SIG_UNBLOCK, &sigset as *const sigset_t, ptr::null_mut() as *mut sigset_t);
+    pub fn get_pid() -> u32 {
+        unistd::getpid() as u32
+    }
+
+    /// Set up pipes such that the relevant output of parent is sent to the stdin of child.
+    /// The content that is sent depends on `mode`
+    pub unsafe fn create_pipe(parent: &mut Command,
+                              child: &mut Command,
+                              mode: RedirectFrom) -> Result<(), Error>
+    {
+        let (reader, writer) = unistd::pipe2(fcntl::O_CLOEXEC)?;
+        match mode {
+            RedirectFrom::Stdout => {
+                parent.stdout(Stdio::from_raw_fd(writer));
+            },
+            RedirectFrom::Stderr => {
+                parent.stderr(Stdio::from_raw_fd(writer));
+            },
+            RedirectFrom::Both => {
+                let temp_file = File::from_raw_fd(writer);
+                let clone = temp_file.try_clone()?;
+                // We want to make sure that the temp file we created no longer has ownership
+                // over the raw file descriptor otherwise it gets closed
+                temp_file.into_raw_fd();
+                parent.stdout(Stdio::from_raw_fd(writer));
+                parent.stderr(Stdio::from_raw_fd(clone.into_raw_fd()));
+            }
+        }
+        child.stdin(Stdio::from_raw_fd(reader));
+        Ok(())
     }
 }
 
 #[cfg(target_os = "redox")]
-fn unmask_sigtstp() {
-    // TODO
-}
+mod crossplat {
+    use parser::peg::{RedirectFrom};
+    use std::fs::File;
+    use std::io;
+    use std::os::unix::io::{IntoRawFd, FromRawFd};
+    use std::process::{Stdio, Command};
+    use syscall;
+
+    /// The purpose of the signal handler is to ignore signals when it is active, and then continue
+    /// listening to signals once the handler is dropped.
+    pub struct SignalHandler;
+
+    impl SignalHandler {
+        pub fn new() -> SignalHandler {
+            // TODO
+            SignalHandler
+        }
+    }
 
-#[cfg(all(unix, not(target_os = "redox")))]
-/// When given a process ID, that process will be assigned to a new process group.
-fn create_process_group() {
-    let _ = unistd::setpgid(0, 0);
-}
+    impl Drop for SignalHandler {
+        fn drop(&mut self) {
+            // TODO
+        }
+    }
 
-#[cfg(target_os = "redox")]
-fn create_process_group() {
-    // TODO
-}
+    pub fn unmask_sigtstp() {
+        // TODO
+    }
 
-#[cfg(all(unix, not(target_os = "redox")))]
-/// When given a process ID, that process's group will be assigned as the foreground process group.
-pub fn set_foreground(pid: u32) {
-    let _ = unistd::tcsetpgrp(0, pid as i32);
-    let _ = unistd::tcsetpgrp(1, pid as i32);
-    let _ = unistd::tcsetpgrp(2, pid as i32);
-}
+    pub fn create_process_group() {
+        // TODO
+    }
 
-#[cfg(target_os = "redox")]
-pub fn set_foreground(pid: u32) {
-    // TODO
-}
+    pub fn set_foreground(pid: u32) {
+        // TODO
+    }
 
-#[cfg(all(unix, not(target_os = "redox")))]
-fn get_pid() -> u32 {
-    unistd::getpid() as u32
-}
+    pub fn get_pid() -> u32 {
+        // TODO
+    }
 
-#[cfg(target_os = "redox")]
-fn get_pid() -> u32 {
-    // TODO
+    #[derive(Debug)]
+    pub enum Error {
+        Io(io::Error),
+        Sys(syscall::Error)
+    }
+
+    impl From<io::Error> for Error {
+        fn from(data: io::Error) -> Error { Error::Io(data) }
+    }
+
+    impl From<syscall::Error> for Error {
+        fn from(data: syscall::Error) -> Error { Error::Sys(data) }
+    }
+
+    /// Set up pipes such that the relevant output of parent is sent to the stdin of child.
+    /// The content that is sent depends on `mode`
+    pub unsafe fn create_pipe(parent: &mut Command,
+                              child: &mut Command,
+                              mode: RedirectFrom) -> Result<(), Error>
+    {
+        // XXX: Zero probably is a bad default for this, but `pipe2` will error if it fails, so
+        // one could reason that it isn't dangerous.
+        let mut fds: [usize; 2] = [0; 2];
+        syscall::call::pipe2(&mut fds, syscall::flag::O_CLOEXEC)?;
+        let (reader, writer) = (fds[0], fds[1]);
+        match mode {
+            RedirectFrom::Stdout => {
+                parent.stdout(Stdio::from_raw_fd(writer));
+            },
+            RedirectFrom::Stderr => {
+                parent.stderr(Stdio::from_raw_fd(writer));
+            },
+            RedirectFrom::Both => {
+                let temp_file = File::from_raw_fd(writer);
+                let clone = temp_file.try_clone()?;
+                // We want to make sure that the temp file we created no longer has ownership
+                // over the raw file descriptor otherwise it gets closed
+                temp_file.into_raw_fd();
+                parent.stdout(Stdio::from_raw_fd(writer));
+                parent.stderr(Stdio::from_raw_fd(clone.into_raw_fd()));
+            }
+        }
+        child.stdin(Stdio::from_raw_fd(reader));
+        Ok(())
+    }
 }
 
 pub trait PipelineExecution {
@@ -155,16 +250,14 @@ impl<'a> PipelineExecution for Shell<'a> {
         self.foreground.clear();
         // If the given pipeline is a background task, fork the shell.
         if piped_commands[piped_commands.len()-1].1 == JobKind::Background {
-            fork_pipe(self, &mut piped_commands)
+            fork_pipe(self, piped_commands)
         } else {
             // While active, the SIGTTOU signal will be ignored.
-            let sig_ignore = SignalHandler::new();
+            let _sig_ignore = SignalHandler::new();
             // Execute each command in the pipeline, giving each command the foreground.
-            let exit_status = pipe(self, &mut piped_commands, true);
+            let exit_status = pipe(self, piped_commands, true);
             // Set the shell as the foreground process again to regain the TTY.
             set_foreground(get_pid());
-            // Dropping this will un-ignore the SIGTTOU signal.
-            drop(sig_ignore);
             exit_status
         }
     }
@@ -193,7 +286,7 @@ fn ion_fork() -> Result<Fork, NixError> {
     }
 }
 
-fn fork_pipe(shell: &mut Shell, commands: &mut [(Command, JobKind)]) -> i32 {
+fn fork_pipe(shell: &mut Shell, commands: Vec<(Command, JobKind)>) -> i32 {
     match ion_fork() {
         Ok(Fork::Parent(pid)) => {
             shell.send_to_background(pid, ProcessState::Running);
@@ -212,122 +305,103 @@ fn fork_pipe(shell: &mut Shell, commands: &mut [(Command, JobKind)]) -> i32 {
 }
 
 /// This function will panic if called with an empty slice
-fn pipe(shell: &mut Shell, commands: &mut [(Command, JobKind)], foreground: bool) -> i32 {
+fn pipe (
+    shell: &mut Shell,
+    commands: Vec<(Command, JobKind)>,
+    foreground: bool
+) -> i32 {
     let mut previous_status = SUCCESS;
     let mut previous_kind = JobKind::And;
-    let mut commands = commands.iter_mut();
-    while let Some(&mut (ref mut command, kind)) = commands.next() {
-        // When an `&&` or `||` operator is utilized, execute commands based on the previous status.
-        match previous_kind {
-            JobKind::And => if previous_status != SUCCESS {
-                if let JobKind::Or = kind { previous_kind = kind }
-                continue
-            },
-            JobKind::Or => if previous_status == SUCCESS {
-                if let JobKind::And = kind { previous_kind = kind }
-                continue
-            },
-            _ => ()
-        }
-
-        match kind {
-            JobKind::Pipe(mut from) => {
-                let mut children: Vec<Option<Child>> = Vec::new();
-
-                // Initialize the first job
-                let _ = match from {
-                    RedirectFrom::Both | RedirectFrom::Stderr => command.stderr(Stdio::piped()), // TODO: Fix this
-                    RedirectFrom::Stdout => command.stdout(Stdio::piped()),
-                };
-
-                let child = command.before_exec(move || {
-                    unmask_sigtstp();
-                    create_process_group();
-                    Ok(())
-                }).spawn().ok();
-                match child {
-                    Some(child) => {
-                        if foreground { set_foreground(child.id()); }
-                        shell.foreground.push(child.id());
-                        children.push(Some(child))
-                    },
-                    None => {
-                        children.push(None);
-                        let stderr = io::stderr();
-                        let mut stderr = stderr.lock();
-                        let _ = writeln!(stderr, "ion: command not found: {}", get_command_name(command));
-                    }
-                }
+    let mut commands = commands.into_iter();
+    loop {
+        if let Some((mut parent, mut kind)) = commands.next() {
+            // When an `&&` or `||` operator is utilized, execute commands based on the previous status.
+            match previous_kind {
+                JobKind::And => if previous_status != SUCCESS {
+                    if let JobKind::Or = kind { previous_kind = kind }
+                    commands.next();
+                    continue
+                },
+                JobKind::Or => if previous_status == SUCCESS {
+                    if let JobKind::And = kind { previous_kind = kind }
+                    commands.next();
+                    continue
+                },
+                _ => ()
+            }
 
-                // Append other jobs until all piped jobs are running.
-                while let Some(&mut (ref mut command, kind)) = commands.next() {
-                    if let JobKind::Pipe(from) = kind {
-                        let _ = match from {
-                            RedirectFrom::Both | RedirectFrom::Stderr => command.stderr(Stdio::piped()), // TODO: Fix this
-                            RedirectFrom::Stdout => command.stdout(Stdio::piped()),
-                        };
-                    }
-                    if let Some(spawned) = children.last() {
-                        if let Some(ref child) = *spawned {
-                            unsafe {
-                                match from {
-                                    // TODO: Find a way to properly implement this.
-                                    RedirectFrom::Both => if let Some(ref stderr) = child.stderr {
-                                        command.stdin(Stdio::from_raw_fd(stderr.as_raw_fd()));
-                                    },
-                                    RedirectFrom::Stderr => if let Some(ref stderr) = child.stderr {
-                                        command.stdin(Stdio::from_raw_fd(stderr.as_raw_fd()));
-                                    },
-                                    RedirectFrom::Stdout => if let Some(ref stdout) = child.stdout {
-                                        command.stdin(Stdio::from_raw_fd(stdout.as_raw_fd()));
-                                    }
+            match kind {
+                JobKind::Pipe(mut mode) => {
+
+                    // We need to remember the commands as they own the file descriptors that are
+                    // created by crossplat::create_pipe. We purposfully drop the pipes that are
+                    // owned by a given command in `wait` in order to close those pipes, sending
+                    // EOF to the next command
+                    let mut remember = Vec::new();
+                    let mut children: Vec<Option<Child>> = Vec::new();
+
+                    macro_rules! spawn_proc {
+                        ($cmd:expr) => {{
+                            let child = $cmd.before_exec(move || {
+                                unmask_sigtstp();
+                                create_process_group();
+                                Ok(())
+                            }).spawn();
+                            match child {
+                                Ok(child) => {
+                                    if foreground { set_foreground(child.id()); }
+                                    shell.foreground.push(child.id());
+                                    children.push(Some(child))
+                                },
+                                Err(e) => {
+                                    children.push(None);
+                                    eprintln!("ion: failed to spawn `{}`: {}",
+                                              get_command_name($cmd),
+                                              e);
                                 }
                             }
-                        } else {
-                            // The previous command failed to spawn
-                            command.stdin(Stdio::null());
-                        }
+                        }};
                     }
-                    let child = command.before_exec(move || {
-                        unmask_sigtstp();
-                        create_process_group();
-                        Ok(())
-                    }).spawn().ok();
-                    match child {
-                        Some(child) => {
-                            if foreground { set_foreground(child.id()); }
-                            shell.foreground.push(child.id());
-                            children.push(Some(child));
-                        },
-                        None => {
-                            children.push(None);
-                            let stderr = io::stderr();
-                            let mut stderr = stderr.lock();
-                            let _ = writeln!(stderr, "ion: command not found: {}", get_command_name(command));
+
+                    // Append other jobs until all piped jobs are running
+                    while let Some((mut child, ckind)) = commands.next() {
+                        if let Err(e) = unsafe {
+                            crossplat::create_pipe(&mut parent, &mut child, mode)
+                        } {
+                            eprintln!("ion: failed to create pipe for redirection: {:?}", e);
+                        }
+                        spawn_proc!(&mut parent);
+                        remember.push(parent);
+                        if let JobKind::Pipe(m) = ckind {
+                            parent = child;
+                            mode = m;
+                        } else {
+                            // We set the kind to the last child kind that was processed. For
+                            // example, the pipeline `foo | bar | baz && zardoz` should have the
+                            // previous kind set to `And` after processing the initial pipeline
+                            kind = ckind;
+                            spawn_proc!(&mut child);
+                            remember.push(child);
+                            break
                         }
                     }
 
-                    if let JobKind::Pipe(next) = kind {
-                        from = next;
-                        continue
-                    } else {
-                        previous_kind = kind;
-                        break
+                    previous_kind = kind;
+                    previous_status = wait(shell, &mut children, remember);
+                    if previous_status == TERMINATED {
+                        terminate_fg(shell);
+                        return previous_status;
                     }
                 }
-                previous_status = wait(shell, &mut children);
-                if previous_status == TERMINATED {
-                    terminate_fg(shell);
-                    return previous_status;
+                _ => {
+                    previous_status = execute_command(shell, &mut parent, foreground);
+                    previous_kind = kind;
                 }
             }
-            _ => {
-                previous_status = execute_command(shell, command, foreground);
-                previous_kind = kind;
-            }
+        } else {
+            break
         }
     }
-
     previous_status
 }
 
@@ -363,10 +437,12 @@ fn wait_on_child(shell: &mut Shell, child: Child, foreground: bool) -> i32 {
 }
 
 /// This function will panic if called with an empty vector
-fn wait(shell: &mut Shell, children: &mut Vec<Option<Child>>) -> i32 {
+fn wait(shell: &mut Shell, children: &mut Vec<Option<Child>>, commands: Vec<Command>) -> i32 {
     let end = children.len() - 1;
-    for child in children.drain(..end) {
-        if let Some(child) = child {
+    for entry in children.drain(..end).zip(commands.into_iter()) {
+        // _cmd is never used here, but it is important that it gets dropped at the end of this
+        // block in order to write EOF to the pipes that it owns.
+        if let (Some(child), _cmd) = entry {
             let status = shell.watch_foreground(child.id());
             if status == TERMINATED {
                 return status