diff --git a/src/builtins/exec.rs b/src/builtins/exec.rs
new file mode 100644
index 0000000000000000000000000000000000000000..9ae8a927d8cf9bb1ac50c653c42fa85dc75e8854
--- /dev/null
+++ b/src/builtins/exec.rs
@@ -0,0 +1,38 @@
+use builtins::man_pages::{check_help, MAN_EXEC};
+use shell::Shell;
+use std::error::Error;
+use sys::execve;
+
+/// Executes the givent commmand.
+pub(crate) fn exec(shell: &mut Shell, args: &[&str]) -> Result<(), String> {
+    const CLEAR_ENV: u8 = 1;
+
+    let mut flags = 0u8;
+    let mut idx = 0;
+    for &arg in args.iter() {
+        match arg {
+            "-c" => flags |= CLEAR_ENV,
+            _ if check_help(args, MAN_EXEC) => {
+                return Ok(());
+            }
+            _ => break,
+        }
+        idx += 1;
+    }
+
+    match args.get(idx) {
+        Some(argument) => {
+            let args = if args.len() > idx + 1 {
+                &args[idx + 1..]
+            } else {
+                &[]
+            };
+            shell.prep_for_exit();
+            match execve(argument, args, (flags & CLEAR_ENV) == 1) {
+                Ok(_) => Ok(()),
+                Err(err) => Err(err.description().to_owned()),
+            }
+        }
+        None => Err("no command provided".to_owned()),
+    }
+}
diff --git a/src/builtins/man_pages.rs b/src/builtins/man_pages.rs
index 6d3ca583e774eab3849b4a82490e6a8bf6f8f70f..7aa8740933d469991d8dd090d7adec6a795718f3 100644
--- a/src/builtins/man_pages.rs
+++ b/src/builtins/man_pages.rs
@@ -197,6 +197,22 @@ DESCRIPTION
     all arguments are joined using a space as a separator.
 "#;
 
+pub(crate) const MAN_EXEC: &'static str = r#"NAME
+    exec - Replace the shell with the given command.
+
+SYNOPSIS
+    exec [-ch] [--help] [command [arguments ...]]
+
+DESCRIPTION
+    Execute <command>, replacing the shell with the specified program.
+    The <arguments> following the command become the arguments to
+    <command>.
+
+OPTIONS
+    -c  Execute command with an empty environment.
+"#;
+
+
 pub(crate) const MAN_HISTORY: &'static str = r#"NAME
     history - print command history
 
diff --git a/src/builtins/mod.rs b/src/builtins/mod.rs
index dafbbbc95f57d6f8f98ed481753c4d1811cff917..9c6be6e322f0ff14aa41858e907b7c5ecbe16a33 100644
--- a/src/builtins/mod.rs
+++ b/src/builtins/mod.rs
@@ -12,11 +12,13 @@ mod echo;
 mod set;
 mod status;
 mod exists;
+mod exec;
 mod ion;
 mod is;
 
 use self::conditionals::{contains, ends_with, starts_with};
 use self::echo::echo;
+use self::exec::exec;
 use self::exists::exists;
 use self::functions::fn_;
 use self::ion::ion_docs;
@@ -80,7 +82,8 @@ pub const BUILTINS: &'static BuiltinMap = &map!(
     "drop" => builtin_drop : "Delete a variable",
     "echo" => builtin_echo : "Display a line of text",
     "ends-with" => ends_with :"Evaluates if the supplied argument ends with a given string",
-    "eval" => builtin_eval : "evaluates the evaluated expression",
+    "eval" => builtin_eval : "Evaluates the evaluated expression",
+    "exec" => builtin_exec : "Replace the shell with the given command.",
     "exists" => builtin_exists : "Performs tests on files and text",
     "exit" => builtin_exit : "Exits the current session",
     "false" => builtin_false : "Do nothing, unsuccessfully",
@@ -499,6 +502,19 @@ fn builtin_exit(args: &[&str], shell: &mut Shell) -> i32 {
     )
 }
 
+fn builtin_exec(args: &[&str], shell: &mut Shell) -> i32 {
+    match exec(shell, &args[1..]) {
+        // Shouldn't ever hit this case.
+        Ok(()) => SUCCESS,
+        Err(err) => {
+            let stderr = io::stderr();
+            let mut stderr = stderr.lock();
+            let _ = writeln!(stderr, "ion: exec: {}", err);
+            FAILURE
+        }
+    }
+}
+
 use regex::Regex;
 fn builtin_matches(args: &[&str], _: &mut Shell) -> i32 {
     if check_help(args, MAN_MATCHES) {
diff --git a/src/shell/mod.rs b/src/shell/mod.rs
index b76ab0ce30ab69221c9451562e474e14a04d99be..40af569cbf286b4a2c4abaad01d38b55594b98ef 100644
--- a/src/shell/mod.rs
+++ b/src/shell/mod.rs
@@ -164,7 +164,7 @@ impl<'a> Shell {
         }
     }
 
-    pub(crate) fn exit(&mut self, status: i32) -> ! {
+    pub(crate) fn prep_for_exit(&mut self) {
         // The context has two purposes: if it exists, this is an interactive shell; and the
         // context will also be sent a signal to commit all changes to the history file,
         // and waiting for the history thread in the background to finish.
@@ -174,9 +174,12 @@ impl<'a> Shell {
                 self.background_send(sys::SIGHUP);
             }
             let context = self.context.as_mut().unwrap();
-            context.history.commit_history()
+            context.history.commit_history();
         }
+    }
 
+    pub(crate) fn exit(&mut self, status: i32) -> ! {
+        self.prep_for_exit();
         process::exit(status);
     }
 
diff --git a/src/sys/redox.rs b/src/sys/redox.rs
index 919f51cdf483a1e2fa06f34c738a7a8789186987..5e950ff130428e18c39dbfca159b93c825d1559f 100644
--- a/src/sys/redox.rs
+++ b/src/sys/redox.rs
@@ -1,7 +1,10 @@
 extern crate syscall;
 
 use std::{io, mem, slice};
+use std::env;
+use std::os::unix::ffi::OsStrExt;
 use std::os::unix::io::RawFd;
+use std::path::PathBuf;
 
 use syscall::SigAction;
 
@@ -49,6 +52,55 @@ pub(crate) fn setpgid(pid: u32, pgid: u32) -> io::Result<()> {
     cvt(syscall::setpgid(pid as usize, pgid as usize)).and(Ok(()))
 }
 
+pub(crate) fn execve(prog: &str, args: &[&str], clear_env: bool) -> io::Result<()> {
+    // Construct a valid set of arguments to pass to execve. Ensure
+    // that the program is the first argument.
+    let mut cvt_args: Vec<[usize; 2]> = Vec::new();
+    cvt_args.push([prog.as_ptr() as usize, prog.len()]);
+    for arg in args {
+        cvt_args.push([arg.as_ptr() as usize, arg.len()]);
+    }
+
+    // Get the PathBuf of the program if it exists.
+    let prog = if prog.contains(':') || prog.contains('/') {
+        // This is a fully specified scheme or path to an
+        // executable.
+        Some(PathBuf::from(prog))
+    } else if let Ok(paths) = env::var("PATH") {
+        // This is not a fully specified scheme or path.
+        // Iterate through the possible paths in the
+        // env var PATH that this executable may be found
+        // in and return the first one found.
+        env::split_paths(&paths)
+            .filter_map(|mut path| {
+                path.push(prog);
+                if path.exists() {
+                    Some(path)
+                } else {
+                    None
+                }
+            })
+            .next()
+    } else {
+        None
+    };
+
+    // If clear_env set, clear the env.
+    if clear_env {
+        for (key, _) in env::vars() {
+            env::remove_var(key);
+        }
+    }
+
+    if let Some(prog) = prog {
+        // If we found the program. Run it!
+        cvt(syscall::execve(prog.as_os_str().as_bytes(), &cvt_args)).and(Ok(()))
+    } else {
+        // The binary was not found.
+        Err(io::Error::from_raw_os_error(syscall::ENOENT))
+    }
+}
+
 #[allow(dead_code)]
 pub(crate) fn signal(signal: i32, handler: extern "C" fn(i32)) -> io::Result<()> {
     let new = SigAction {
diff --git a/src/sys/unix/mod.rs b/src/sys/unix/mod.rs
index 83abc799d7488869821bee60e23ebd0ef8abc958..5207ae09c1bb1d07e6d8f33566d121857ee5a77f 100644
--- a/src/sys/unix/mod.rs
+++ b/src/sys/unix/mod.rs
@@ -3,8 +3,10 @@ extern crate libc;
 pub mod job_control;
 pub mod signals;
 
-use libc::{c_int, pid_t, sighandler_t};
-use std::io;
+use libc::{c_char, c_int, pid_t, sighandler_t};
+use std::{io, ptr};
+use std::env;
+use std::ffi::CString;
 use std::os::unix::io::RawFd;
 
 pub(crate) const PATH_SEPARATOR: &str = ":";
@@ -41,6 +43,83 @@ pub(crate) fn killpg(pgid: u32, signal: i32) -> io::Result<()> {
     cvt(unsafe { libc::kill(-(pgid as pid_t), signal as c_int) }).and(Ok(()))
 }
 
+pub(crate) fn execve(prog: &str, args: &[&str], clear_env: bool) -> io::Result<()> {
+    // Prepare the program string
+    let prog_str = match CString::new(prog) {
+        Ok(prog_str) => prog_str,
+        Err(_) => { return Err(io::Error::last_os_error()); }
+    };
+
+    // Create the arguments vector
+    let mut cvt_args: Vec<CString> = Vec::new();
+    cvt_args.push(prog_str);
+    for arg in args.iter() {
+        match CString::new(*arg) {
+            Ok(arg) => cvt_args.push(arg),
+            Err(_) => {
+                return Err(io::Error::last_os_error());
+            }
+        }
+    }
+    let mut arg_ptrs: Vec<*const c_char> = cvt_args.iter().map(|x| x.as_ptr()).collect();
+    // NULL terminate the argv array
+    arg_ptrs.push(ptr::null());
+
+    // Get the PathBuf of the program if it exists.
+    let prog = if prog.contains('/') {
+        // This is a fully specified path to an executable.
+        match CString::new(prog) {
+            Ok(prog_str) => Some(prog_str),
+            Err(_) => None,
+        }
+    } else if let Ok(paths) = env::var("PATH") {
+        // This is not a fully specified scheme or path.
+        // Iterate through the possible paths in the
+        // env var PATH that this executable may be found
+        // in and return the first one found.
+        env::split_paths(&paths)
+            .filter_map(|mut path| {
+                path.push(prog);
+                match (path.exists(), path.to_str()) {
+                    (false, _) => None,
+                    (true, Some(path)) => match CString::new(path) {
+                        Ok(prog_str) => Some(prog_str),
+                        Err(_) => None,
+                    },
+                    (true, None) => None,
+                }
+            })
+            .next()
+    } else {
+        None
+    };
+
+    let mut env_ptrs: Vec<*const c_char> = Vec::new();
+    // If clear_env is not specified build envp
+    if !clear_env {
+        let mut env_vars: Vec<CString> = Vec::new();
+        for (key, value) in env::vars() {
+            match CString::new(format!("{}={}", key, value)) {
+                Ok(var) => env_vars.push(var),
+                Err(_) => {
+                    return Err(io::Error::last_os_error());
+                }
+            }
+        }
+        env_ptrs = env_vars.iter().map(|x| x.as_ptr()).collect();
+    }
+    env_ptrs.push(ptr::null());
+
+    if let Some(prog) = prog {
+        // If we found the program. Run it!
+        cvt(unsafe { libc::execve(prog.as_ptr(), arg_ptrs.as_ptr(), env_ptrs.as_ptr()) })
+            .and(Ok(()))
+    } else {
+        // The binary was not found.
+        Err(io::Error::from_raw_os_error(libc::ENOENT))
+    }
+}
+
 pub(crate) fn pipe2(flags: usize) -> io::Result<(RawFd, RawFd)> {
     let mut fds = [0; 2];