diff --git a/Cargo.lock b/Cargo.lock
index c82509ce32e3517dacd7c2d510316c554c0064a1..805ebf2830c3efb301b796f9fd1f210c603ed007 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -175,6 +175,14 @@ name = "gcc"
 version = "0.3.55"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
+[[package]]
+name = "getopts"
+version = "0.2.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "glob"
 version = "0.2.11"
@@ -205,6 +213,7 @@ dependencies = [
  "calculate 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "getopts 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)",
  "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
  "hashbrown 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "ion-ranges 0.1.0",
@@ -539,7 +548,7 @@ dependencies = [
 [[package]]
 name = "termion"
 version = "1.5.1"
-source = "git+https://gitlab.redox-os.org/redox-os/termion#c04fd9dce9f44e01687ef3f14ff063135932c6b1"
+source = "git+https://gitlab.redox-os.org/redox-os/termion#cd8a90a28736e753c55d911bdb95e216eef0ac0b"
 dependencies = [
  "libc 0.2.47 (registry+https://github.com/rust-lang/crates.io-index)",
  "numtoa 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -666,6 +675,7 @@ source = "git+https://github.com/whitequark/rust-xdg#f404ae631b30f5fcb191f9fb65f
 "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
 "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
 "checksum gcc 0.3.55 (registry+https://github.com/rust-lang/crates.io-index)" = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2"
+"checksum getopts 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "0a7292d30132fb5424b354f5dc02512a86e4c516fe544bb7a25e7f266951b797"
 "checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb"
 "checksum hashbrown 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3bae29b6653b3412c2e71e9d486db9f9df5d701941d86683005efb9f2d28e3da"
 "checksum itertools 0.7.11 (registry+https://github.com/rust-lang/crates.io-index)" = "0d47946d458e94a1b7bcabbf6521ea7c037062c81f534615abcad76e84d4970d"
diff --git a/Cargo.toml b/Cargo.toml
index 0b5c34cd95c7cc570a38b5183c9cdf562b76788a..66c7f7c002f128fa6b471c9cc000d58c6038752e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -55,6 +55,7 @@ ion_sys = { path = "members/sys" }
 ion-ranges = { path = "members/ranges" }
 hashbrown = "0.1.2"
 itertools = "0.7.9"
+getopts = "0.2"
 
 [lib]
 path = "src/lib/lib.rs"
diff --git a/examples/check.out b/examples/check.out
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/examples/check.params b/examples/check.params
new file mode 100644
index 0000000000000000000000000000000000000000..2112a8c6905baf850e603528e551f97914fbb89f
--- /dev/null
+++ b/examples/check.params
@@ -0,0 +1,3 @@
+-n
+-c
+echo silent
diff --git a/examples/command.out b/examples/command.out
new file mode 100644
index 0000000000000000000000000000000000000000..d00491fd7e5bb6fa28c517a0bb32b8b506539d4d
--- /dev/null
+++ b/examples/command.out
@@ -0,0 +1 @@
+1
diff --git a/examples/command.params b/examples/command.params
new file mode 100644
index 0000000000000000000000000000000000000000..d75a8eb0eb738b13edb38df4a8739fffb9ea45c0
--- /dev/null
+++ b/examples/command.params
@@ -0,0 +1,4 @@
+-c
+echo 1
+Hello
+World
diff --git a/examples/help.out b/examples/help.out
new file mode 100644
index 0000000000000000000000000000000000000000..8c830bf9641e55d534b69b1f1cbf5f9f80d29c48
--- /dev/null
+++ b/examples/help.out
@@ -0,0 +1,22 @@
+NAME
+    Ion - The Ion shell
+
+SYNOPSIS
+    ion [options] [args...]
+
+DESCRIPTION
+    Ion is a commandline shell created to be a faster and easier to use alternative to the
+    currently available shells. It is not POSIX compliant.
+
+Args:
+    Script arguments (@args). If the -c option is not specified, the first parameter
+    is taken as a filename to execute
+
+Options:
+    -c, --command COMMAND
+                        evaluates given commands instead of reading from the
+                        commandline
+    -n, --no-execute    do not execute any commands, just do syntax checking.
+    -h, --help          print this help menu
+    -v, --version       print the version
+
diff --git a/examples/help.params b/examples/help.params
new file mode 100644
index 0000000000000000000000000000000000000000..e541fc88557bcfa52806a21acfaf8ca0566abd0f
--- /dev/null
+++ b/examples/help.params
@@ -0,0 +1 @@
+-h
diff --git a/examples/run_examples.sh b/examples/run_examples.sh
index b4894175e92ae0da2aa294810a8f43d932f91629..311e50e8ef2ae21ee0705755524e2378b88a3c95 100755
--- a/examples/run_examples.sh
+++ b/examples/run_examples.sh
@@ -21,19 +21,12 @@ EXIT_VAL=0
 # and it never hurts to force consistency regardless
 cd $PROJECT_DIR
 
-function check_return_value {
-
-    # Check number of parameters passed into the check function
-    if [[ $# -ne 1 ]]; then
-        echo -e "Illegal number of parameters.${TAGFAIL}";
-        return 1;
-    fi
-
+function test {
     # Replace .ion with .out in file name
-    EXPECTED_OUTPUT_FILE=$(echo $1 | sed 's/\.ion/\.out/')
+    EXPECTED_OUTPUT_FILE=$(echo $1 | sed 's/\..\+/\.out/')
 
     # Run script and redirect stdout into tmp file
-    $PROJECT_DIR/target/debug/ion $1 1> $EXAMPLES_DIR/tmp.out 2> /dev/null
+    $PROJECT_DIR/target/debug/ion "${@:2}" > $EXAMPLES_DIR/tmp.out 2> /dev/null
 
     # Compare real and expected output
     diff "$EXAMPLES_DIR"/tmp.out "$EXPECTED_OUTPUT_FILE" > "$EXAMPLES_DIR"/diff_tmp
@@ -43,7 +36,7 @@ function check_return_value {
     rm -f $EXAMPLES_DIR/tmp.out
 
     # Write result
-    if [[ $RET -ne 0 ]]; then
+    if [[ "$RET" -ne "0" ]]; then
         cat "$EXAMPLES_DIR"/diff_tmp
         rm "$EXAMPLES_DIR"/diff_tmp
         echo -e "Test ${1} ${TAGFAIL}";
@@ -55,6 +48,28 @@ function check_return_value {
     fi
 }
 
+function test_cli {
+    # Check number of parameters passed into the check function
+    if [[ $# -ne 1 ]]; then
+        echo -e "Illegal number of parameters.${TAGFAIL}";
+        return 1;
+    fi
+
+    # Run script and redirect stdout into tmp file
+    IFS=$'\n'; test $1 $(< $1)
+}
+
+function check_return_value {
+    # Check number of parameters passed into the check function
+    if [[ $# -ne 1 ]]; then
+        echo -e "Illegal number of parameters.${TAGFAIL}";
+        return 1;
+    fi
+
+    # Run script and redirect stdout into tmp file
+    test $1 $1 1
+}
+
 # Build debug binary
 cargo +$TOOLCHAIN build
 
@@ -67,4 +82,12 @@ for i in $EXAMPLES_DIR/*.ion; do
     fi
 done
 
+# Iterate over every parameter set
+for i in $EXAMPLES_DIR/*.params; do
+    test_cli $i;
+    if [[ $? -ne 0 ]]; then
+        EXIT_VAL=1;
+    fi
+done
+
 exit $EXIT_VAL
diff --git a/src/lib/lib.rs b/src/lib/lib.rs
index 509d25d56961826e0bd908a820c1b1a7bf4f632f..a6581a95e0c61285dba5f114f2309781db5f06f5 100644
--- a/src/lib/lib.rs
+++ b/src/lib/lib.rs
@@ -37,3 +37,5 @@ pub use crate::shell::{
     binary::MAN_ION, flags, pipe_exec::job_control::JobControl, status, Binary, Capture, Fork,
     IonError, IonResult, Shell, ShellBuilder,
 };
+
+pub fn version() -> &'static str { include!(concat!(env!("OUT_DIR"), "/version_string")) }
diff --git a/src/lib/shell/binary/mod.rs b/src/lib/shell/binary/mod.rs
index 04d8019198c0a34cf07954c310e789cfbea5d00c..360860be0b2885451891a96dffca384257a1483a 100644
--- a/src/lib/shell/binary/mod.rs
+++ b/src/lib/shell/binary/mod.rs
@@ -12,37 +12,30 @@ use self::{
 use super::{status::*, FlowLogic, Shell, ShellHistory};
 use crate::types;
 use liner::{Buffer, Context};
-use std::{env, iter, path::Path, process};
+use std::{env, iter, path::Path};
 
-pub const MAN_ION: &str = r#"NAME
-    ion - ion shell
+pub const MAN_ION: &str = "NAME
+    Ion - The Ion shell
 
 SYNOPSIS
-    ion [ -h | --help ] [-c] [-n] [-v]
+    ion [options] [args...]
 
 DESCRIPTION
-    ion is a commandline shell created to be a faster and easier to use alternative to the
+    Ion is a commandline shell created to be a faster and easier to use alternative to the
     currently available shells. It is not POSIX compliant.
 
-OPTIONS
-    -c
-        evaluates given commands instead of reading from the commandline.
-
-    -n or --no-execute
-        do not execute any commands, just do syntax checking.
-
-    -v or --version
-        prints the version, platform and revision of ion then exits.
-"#;
+Args:
+    Script arguments (@args). If the -c option is not specified, the first parameter
+    is taken as a filename to execute";
 
 pub trait Binary {
     /// Parses and executes the arguments that were supplied to the shell.
-    fn execute_arguments<A: Iterator<Item = String>>(&mut self, args: A);
+    fn execute_script(&mut self, script: &str);
     /// Creates an interactive session that reads from a prompt provided by
     /// Liner.
     fn execute_interactive(self);
     /// Ensures that read statements from a script are terminated.
-    fn terminate_script_quotes<I: Iterator<Item = String>>(&mut self, lines: I) -> i32;
+    fn terminate_script_quotes<T: AsRef<str> + ToString, I: Iterator<Item = T>>(&mut self, lines: I) -> i32;
     /// Ensures that read statements from the interactive prompt is terminated.
     fn terminate_quotes(&mut self, command: String) -> Result<String, ()>;
     /// Ion's interface to Liner's `read_line` method, which handles everything related to
@@ -50,8 +43,6 @@ pub trait Binary {
     fn readln(&mut self) -> Option<String>;
     /// Generates the prompt that will be used by Liner.
     fn prompt(&mut self) -> String;
-    /// Display version information and exit
-    fn display_version(&self);
     // Executes the PROMPT function, if it exists, and returns the output.
     fn prompt_fn(&mut self) -> Option<String>;
     // Handles commands given by the REPL, and saves them to history.
@@ -61,11 +52,6 @@ pub trait Binary {
 }
 
 impl Binary for Shell {
-    fn display_version(&self) {
-        println!("{}", include!(concat!(env!("OUT_DIR"), "/version_string")));
-        process::exit(0);
-    }
-
     fn save_command(&mut self, cmd: &str) {
         if cmd.starts_with('~') {
             if !cmd.ends_with('/')
@@ -110,37 +96,25 @@ impl Binary for Shell {
             .set("args", iter::once(env::args().next().unwrap().into()).collect::<types::Array>());
 
         loop {
-            if let Some(command) = self.readln() {
-                if !command.is_empty() {
-                    if let Ok(command) = self.terminate_quotes(command.replace("\\\n", "")) {
-                        let cmd: &str = &designators::expand_designators(&self, command.trim_end());
-                        self.on_command(&cmd);
-                        self.save_command(&cmd);
-                    } else {
-                        self.reset_flow();
-                    }
+            match self.readln().and_then(|command| {
+                if command.is_empty() {
+                    None
+                } else {
+                    self.terminate_quotes(command.replace("\\\n", "")).ok()
                 }
-            } else {
-                self.reset_flow();
+            }) {
+                Some(command) => {
+                    let cmd: &str = &designators::expand_designators(&self, command.trim_end());
+                    self.on_command(&cmd);
+                    self.save_command(&cmd);
+                },
+                None => self.reset_flow(),
             }
         }
     }
 
-    fn execute_arguments<A: Iterator<Item = String>>(&mut self, mut args: A) {
-        if let Some(mut arg) = args.next() {
-            for argument in args {
-                arg.push(' ');
-                if argument == "" {
-                    arg.push_str("''");
-                } else {
-                    arg.push_str(&argument);
-                }
-            }
-            self.on_command(&arg);
-        } else {
-            eprintln!("ion: -c requires an argument");
-            self.exit(FAILURE);
-        }
+    fn execute_script(&mut self, script: &str) {
+        self.on_command(script);
 
         if self.flow_control.unclosed_block() {
             {
@@ -158,7 +132,7 @@ impl Binary for Shell {
         terminate_quotes(self, command)
     }
 
-    fn terminate_script_quotes<I: Iterator<Item = String>>(&mut self, lines: I) -> i32 {
+    fn terminate_script_quotes<T: AsRef<str> + ToString, I: Iterator<Item = T>>(&mut self, lines: I) -> i32 {
         terminate_script_quotes(self, lines)
     }
 
diff --git a/src/lib/shell/binary/prompt.rs b/src/lib/shell/binary/prompt.rs
index 71b342df7c1a48777d34373576c0ec122c23f1b6..32ec04ace9b901bfe6c4125ca4bcb214567bf7be 100644
--- a/src/lib/shell/binary/prompt.rs
+++ b/src/lib/shell/binary/prompt.rs
@@ -10,10 +10,9 @@ pub(crate) fn prompt(shell: &mut Shell) -> String {
         shell.flow_control.block.len() + if shell.flags & UNTERMINATED != 0 { 1 } else { 0 };
 
     if blocks == 0 {
-        match prompt_fn(shell) {
-            Some(prompt) => prompt,
-            None => expand_string(&shell.get_str_or_empty("PROMPT"), shell, false).join(" "),
-        }
+        prompt_fn(shell).unwrap_or_else(|| {
+            expand_string(&shell.get_str_or_empty("PROMPT"), shell, false).join(" ")
+        })
     } else {
         "    ".repeat(blocks)
     }
diff --git a/src/lib/shell/binary/readln.rs b/src/lib/shell/binary/readln.rs
index c184c6ce2c32bb33ec96852c06e641e25ab6611c..674ae32287e71d617291db3f53981e1d754827f3 100644
--- a/src/lib/shell/binary/readln.rs
+++ b/src/lib/shell/binary/readln.rs
@@ -4,141 +4,129 @@ use liner::{BasicCompleter, CursorPosition, Event, EventKind};
 use std::{env, io::ErrorKind, mem, path::PathBuf};
 
 pub(crate) fn readln(shell: &mut Shell) -> Option<String> {
-    {
-        let vars_ptr = &shell.variables as *const Variables;
-        let dirs_ptr = &shell.directory_stack as *const DirectoryStack;
+    let vars_ptr = &shell.variables as *const Variables;
+    let dirs_ptr = &shell.directory_stack as *const DirectoryStack;
 
-        // Collects the current list of values from history for completion.
-        let history = shell
-            .context
-            .as_ref()
-            .unwrap()
-            .history
-            .buffers
-            .iter()
-            // Map each underlying `liner::Buffer` into a `String`.
-            .map(|x| x.chars().cloned().collect())
-            // Collect each result into a vector to avoid borrowing issues.
-            .collect::<Vec<types::Str>>();
+    // Collects the current list of values from history for completion.
+    let history = shell
+        .context
+        .as_ref()
+        .unwrap()
+        .history
+        .buffers
+        .iter()
+        // Map each underlying `liner::Buffer` into a `String`.
+        .map(|x| x.chars().cloned().collect())
+        // Collect each result into a vector to avoid borrowing issues.
+        .collect::<Vec<types::Str>>();
 
-        {
-            let prompt = shell.prompt();
-            let vars = &shell.variables;
-            let builtins = &shell.builtins;
+    let prompt = shell.prompt();
+    let vars = &shell.variables;
+    let builtins = &shell.builtins;
 
-            let line = shell.context.as_mut().unwrap().read_line(
-                prompt,
-                None,
-                &mut move |Event { editor, kind }| {
-                    if let EventKind::BeforeComplete = kind {
-                        let (words, pos) = editor.get_words_and_cursor_position();
+    let line = shell.context.as_mut().unwrap().read_line(
+        prompt,
+        None,
+        &mut move |Event { editor, kind }| {
+            if let EventKind::BeforeComplete = kind {
+                let (words, pos) = editor.get_words_and_cursor_position();
 
-                        let filename = match pos {
-                            CursorPosition::InWord(index) => index > 0,
-                            CursorPosition::InSpace(Some(_), _) => true,
-                            CursorPosition::InSpace(None, _) => false,
-                            CursorPosition::OnWordLeftEdge(index) => index >= 1,
-                            CursorPosition::OnWordRightEdge(index) => {
-                                match (words.into_iter().nth(index), env::current_dir()) {
-                                    (Some((start, end)), Ok(file)) => {
-                                        let filename = editor.current_buffer().range(start, end);
-                                        complete_as_file(&file, &filename, index)
-                                    }
-                                    _ => false,
-                                }
+                let filename = match pos {
+                    CursorPosition::InWord(index) => index > 0,
+                    CursorPosition::InSpace(Some(_), _) => true,
+                    CursorPosition::InSpace(None, _) => false,
+                    CursorPosition::OnWordLeftEdge(index) => index >= 1,
+                    CursorPosition::OnWordRightEdge(index) => {
+                        match (words.into_iter().nth(index), env::current_dir()) {
+                            (Some((start, end)), Ok(file)) => {
+                                let filename = editor.current_buffer().range(start, end);
+                                complete_as_file(&file, &filename, index)
                             }
-                        };
-
-                        if filename {
-                            if let Ok(current_dir) = env::current_dir() {
-                                if let Some(url) = current_dir.to_str() {
-                                    let completer =
-                                        IonFileCompleter::new(Some(url), dirs_ptr, vars_ptr);
-                                    mem::replace(
-                                        &mut editor.context().completer,
-                                        Some(Box::new(completer)),
-                                    );
-                                }
-                            }
-                        } else {
-                            // Creates a list of definitions from the shell environment that
-                            // will be used
-                            // in the creation of a custom completer.
-                            let words = builtins
-                                .keys()
-                                .iter()
-                                // Add built-in commands to the completer's definitions.
-                                .map(|&s| s.to_string())
-                                // Add the history list to the completer's definitions.
-                                .chain(history.iter().map(|s| s.to_string()))
-                                // Add the aliases to the completer's definitions.
-                                .chain(vars.aliases().map(|(key, _)| key.to_string()))
-                                // Add the list of available functions to the completer's
-                                // definitions.
-                                .chain(vars.functions().map(|(key, _)| key.to_string()))
-                                // Add the list of available variables to the completer's
-                                // definitions. TODO: We should make
-                                // it free to do String->SmallString
-                                //       and mostly free to go back (free if allocated)
-                                .chain(vars.string_vars().map(|(s, _)| ["$", &s].concat()))
-                                .collect();
+                            _ => false,
+                        }
+                    }
+                };
 
-                            // Initialize a new completer from the definitions collected.
-                            let custom_completer = BasicCompleter::new(words);
+                let dir_completer = env::current_dir().ok().as_ref()
+                    .and_then(|dir| dir.to_str())
+                    .map(|dir| IonFileCompleter::new(Some(dir), dirs_ptr, vars_ptr));
 
-                            // Creates completers containing definitions from all directories
-                            // listed
-                            // in the environment's **$PATH** variable.
-                            let mut file_completers = if let Ok(val) = env::var("PATH") {
-                                val.split(sys::PATH_SEPARATOR)
-                                    .map(|s| IonFileCompleter::new(Some(s), dirs_ptr, vars_ptr))
-                                    .collect()
-                            } else {
-                                vec![IonFileCompleter::new(Some("/bin/"), dirs_ptr, vars_ptr)]
-                            };
+                if filename {
+                    if let Some(completer) = dir_completer {
+                        mem::replace(
+                            &mut editor.context().completer,
+                            Some(Box::new(completer)),
+                        );
+                    }
+                } else {
+                    // Creates a list of definitions from the shell environment that
+                    // will be used
+                    // in the creation of a custom completer.
+                    let words = builtins
+                        .keys()
+                        .iter()
+                        // Add built-in commands to the completer's definitions.
+                        .map(|&s| s.to_string())
+                        // Add the history list to the completer's definitions.
+                        .chain(history.iter().map(|s| s.to_string()))
+                        // Add the aliases to the completer's definitions.
+                        .chain(vars.aliases().map(|(key, _)| key.to_string()))
+                        // Add the list of available functions to the completer's
+                        // definitions.
+                        .chain(vars.functions().map(|(key, _)| key.to_string()))
+                        // Add the list of available variables to the completer's
+                        // definitions. TODO: We should make
+                        // it free to do String->SmallString
+                        //       and mostly free to go back (free if allocated)
+                        .chain(vars.string_vars().map(|(s, _)| ["$", &s].concat()))
+                        .collect();
 
-                            // Also add files/directories in the current directory to the
-                            // completion list.
-                            if let Ok(current_dir) = env::current_dir() {
-                                if let Some(url) = current_dir.to_str() {
-                                    file_completers.push(IonFileCompleter::new(
-                                        Some(url),
-                                        dirs_ptr,
-                                        vars_ptr,
-                                    ));
-                                }
-                            }
+                    // Initialize a new completer from the definitions collected.
+                    let custom_completer = BasicCompleter::new(words);
 
-                            // Merge the collected definitions with the file path definitions.
-                            let completer = MultiCompleter::new(file_completers, custom_completer);
+                    // Creates completers containing definitions from all directories
+                    // listed
+                    // in the environment's **$PATH** variable.
+                    let mut file_completers: Vec<_> = env::var("PATH")
+                        .unwrap_or_else(|_| "/bin/".to_string())
+                        .split(sys::PATH_SEPARATOR)
+                        .map(|s| IonFileCompleter::new(Some(s), dirs_ptr, vars_ptr))
+                        .collect();
 
-                            // Replace the shell's current completer with the newly-created
-                            // completer.
-                            mem::replace(
-                                &mut editor.context().completer,
-                                Some(Box::new(completer)),
-                            );
-                        }
+                    // Also add files/directories in the current directory to the
+                    // completion list.
+                    if let Some(completer) = dir_completer {
+                        file_completers.push(completer);
                     }
-                },
-            );
 
-            match line {
-                Ok(line) => return Some(line),
-                // Handles Ctrl + C
-                Err(ref err) if err.kind() == ErrorKind::Interrupted => return None,
-                // Handles Ctrl + D
-                Err(ref err) if err.kind() == ErrorKind::UnexpectedEof => (),
-                Err(err) => {
-                    eprintln!("ion: liner: {}", err);
-                    return None;
+                    // Merge the collected definitions with the file path definitions.
+                    let completer = MultiCompleter::new(file_completers, custom_completer);
+
+                    // Replace the shell's current completer with the newly-created
+                    // completer.
+                    mem::replace(
+                        &mut editor.context().completer,
+                        Some(Box::new(completer)),
+                    );
                 }
             }
+        },
+    );
+
+    match line {
+        Ok(line) => Some(line),
+        // Handles Ctrl + C
+        Err(ref err) if err.kind() == ErrorKind::Interrupted => None,
+        // Handles Ctrl + D
+        Err(ref err) if err.kind() == ErrorKind::UnexpectedEof => {
+            let previous_status = shell.previous_status;
+            shell.exit(previous_status);
+        }
+        Err(err) => {
+            eprintln!("ion: liner: {}", err);
+            None
         }
     }
-
-    let previous_status = shell.previous_status;
-    shell.exit(previous_status);
 }
 
 /// Infer if the given filename is actually a partial filename
@@ -146,29 +134,26 @@ fn complete_as_file(current_dir: &PathBuf, filename: &str, index: usize) -> bool
     let filename = filename.trim();
     let mut file = current_dir.clone();
     file.push(&filename);
-    // If the user explicitly requests a file through this syntax then complete as
-    // a file
     if filename.starts_with('.') {
-        return true;
-    }
-    // If the file starts with a dollar sign, it's a variable, not a file
-    if filename.starts_with('$') {
-        return false;
-    }
-    // Once we are beyond the first string, assume its a file
-    if index > 0 {
-        return true;
-    }
-    // If we are referencing a file that exists then just complete to that file
-    if file.exists() {
-        return true;
-    }
-    // If we have a partial file inside an existing directory, e.g. /foo/b when
-    // /foo/bar exists, then treat it as file as long as `foo` isn't the
-    // current directory, otherwise this would apply to any string `foo`
-    if let Some(parent) = file.parent() {
-        return parent.exists() && parent != current_dir;
+        // If the user explicitly requests a file through this syntax then complete as
+        // a file
+        true
+    } else if filename.starts_with('$') {
+        // If the file starts with a dollar sign, it's a variable, not a file
+        false
+    } else if index > 0 {
+        // Once we are beyond the first string, assume its a file
+        true
+    } else if file.exists() {
+        // If we are referencing a file that exists then just complete to that file
+        true
+    } else if let Some(parent) = file.parent() {
+        // If we have a partial file inside an existing directory, e.g. /foo/b when
+        // /foo/bar exists, then treat it as file as long as `foo` isn't the
+        // current directory, otherwise this would apply to any string `foo`
+        parent.exists() && parent != current_dir
+    } else {
+        // By default assume its not a file
+        false
     }
-    // By default assume its not a file
-    false
 }
diff --git a/src/lib/shell/binary/terminate.rs b/src/lib/shell/binary/terminate.rs
index bc295ad4b88f26bb452ff3c32be19babe71b75a2..a9a43eae8591d004208cb60cc96d82a884e9b84d 100644
--- a/src/lib/shell/binary/terminate.rs
+++ b/src/lib/shell/binary/terminate.rs
@@ -3,15 +3,15 @@ use crate::{
     shell::{flags::UNTERMINATED, status::*, Binary, FlowLogic, Shell},
 };
 
-pub(crate) fn terminate_script_quotes<I: Iterator<Item = String>>(
+pub(crate) fn terminate_script_quotes<T: AsRef<str> + ToString, I: Iterator<Item = T>>(
     shell: &mut Shell,
     mut lines: I,
 ) -> i32 {
     while let Some(command) = lines.next() {
-        let mut buffer = Terminator::new(command);
+        let mut buffer = Terminator::new(command.to_string());
         while !buffer.is_terminated() {
-            if let Some(command) = lines.find(|cmd| !cmd.starts_with('#')) {
-                buffer.append(command.split(" #").next().unwrap_or(&command));
+            if let Some(command) = lines.find(|cmd| !cmd.as_ref().starts_with('#')) {
+                buffer.append(command.as_ref().splitn(2, " #").next().unwrap());
             } else {
                 eprintln!("ion: unterminated quote in script");
                 return FAILURE;
@@ -23,10 +23,10 @@ pub(crate) fn terminate_script_quotes<I: Iterator<Item = String>>(
     if shell.flow_control.unclosed_block() {
         let open_block = shell.flow_control.block.last().unwrap();
         eprintln!("ion: unexpected end of script: expected end block for `{}`", open_block.short(),);
-        return FAILURE;
+        FAILURE
+    } else {
+        SUCCESS
     }
-
-    SUCCESS
 }
 
 pub(crate) fn terminate_quotes(shell: &mut Shell, command: String) -> Result<String, ()> {
@@ -44,6 +44,5 @@ pub(crate) fn terminate_quotes(shell: &mut Shell, command: String) -> Result<Str
     }
 
     shell.flags ^= UNTERMINATED;
-    let terminated = buffer.consume();
-    Ok(terminated)
+    Ok(buffer.consume())
 }
diff --git a/src/lib/shell/mod.rs b/src/lib/shell/mod.rs
index 5cd100fe8ae3ea08b57f3067e8329a1e3b604dfa..2f4a28c4bfa4382ec8d005da885fd7417dd2f54b 100644
--- a/src/lib/shell/mod.rs
+++ b/src/lib/shell/mod.rs
@@ -57,7 +57,7 @@ use crate::{
 };
 use liner::Context;
 use std::{
-    fs::File,
+    fs,
     io::{self, Read, Write},
     iter::FromIterator,
     ops::Deref,
@@ -212,15 +212,15 @@ impl Shell {
     /// A method for executing scripts in the Ion shell without capturing. Given a `Path`, this
     /// method will attempt to execute that file as a script, and then returns the final exit
     /// status of the evaluated script.
-    pub fn execute_script<SCRIPT: AsRef<Path>>(&mut self, script: SCRIPT) -> io::Result<i32> {
-        let mut script = File::open(script.as_ref())?;
-        let capacity = script.metadata().ok().map_or(0, |x| x.len());
-        let mut command_list = String::with_capacity(capacity as usize);
-        let _ = script.read_to_string(&mut command_list)?;
-        if FAILURE == self.terminate_script_quotes(command_list.lines().map(|x| x.to_owned())) {
-            self.previous_status = FAILURE;
+    pub fn execute_file<P: AsRef<Path>>(&mut self, script: P) {
+        match fs::read_to_string(script.as_ref()) {
+            Ok(script) => {
+                if self.terminate_script_quotes(script.lines()) == FAILURE {
+                    self.previous_status = FAILURE;
+                }
+            }
+            Err(err) => eprintln!("ion: {}", err),
         }
-        Ok(self.previous_status)
     }
 
     /// A method for executing commands in the Ion shell without capturing. It takes command(s)
@@ -348,11 +348,7 @@ impl Shell {
             }
         };
         match base_dirs.find_config_file("initrc") {
-            Some(initrc) => {
-                if let Err(err) = self.execute_script(&initrc) {
-                    eprintln!("ion: {}", err);
-                }
-            }
+            Some(initrc) => self.execute_file(&initrc),
             None => {
                 if let Err(err) = base_dirs.place_config_file("initrc") {
                     eprintln!("ion: could not create initrc file: {}", err);
diff --git a/src/main.rs b/src/main.rs
index 4bf8830ccf1f72ce9c81f10e655bc686c82d14cc..16a518148b91a9e9e7ec77defbe550494c897690 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,15 +1,16 @@
+extern crate getopts;
 extern crate ion_shell;
 extern crate ion_sys as sys;
+extern crate small;
 extern crate smallvec;
 
+use getopts::Options;
 use ion_shell::{flags::NO_EXEC, Binary, JobControl, ShellBuilder, MAN_ION};
 use smallvec::SmallVec;
 use std::{
     alloc::System,
     env,
-    error::Error,
-    io::{stdin, stdout, BufRead, BufReader, Write},
-    iter::FromIterator,
+    io::{stdin, BufRead, BufReader},
 };
 
 #[global_allocator]
@@ -25,46 +26,60 @@ fn main() {
 
     let mut shell = shell.as_binary();
 
-    let mut args = env::args().skip(1);
-    while let Some(path) = args.next() {
-        match path.as_str() {
-            "-n" | "--no-execute" => {
-                shell.flags |= NO_EXEC;
-                continue;
-            }
-            "-c" => shell.execute_arguments(args),
-            "-v" | "--version" => shell.display_version(),
-            "-h" | "--help" => {
-                let stdout = stdout();
-                let mut stdout = stdout.lock();
-                match stdout.write_all(MAN_ION.as_bytes()).and_then(|_| stdout.flush()) {
-                    Ok(_) => return,
-                    Err(err) => panic!("{}", err.description().to_owned()),
-                }
-            }
-            _ => {
-                let mut array = SmallVec::from_iter(Some(path.clone().into()));
-                for arg in args {
-                    array.push(arg.into());
-                }
-                shell.variables.set("args", array);
-                if let Err(err) = shell.execute_script(&path) {
-                    eprintln!("ion: {}", err);
-                }
-            }
-        }
+    let args: Vec<String> = env::args().collect();
 
-        shell.wait_for_background();
-        let previous_status = shell.previous_status;
-        shell.exit(previous_status);
+    let mut opts = Options::new();
+    opts.optopt(
+        "c",
+        "command",
+        "evaluates given commands instead of reading from the commandline",
+        "COMMAND",
+    );
+    opts.optflag("n", "no-execute", "do not execute any commands, just do syntax checking.");
+    opts.optflag("h", "help", "print this help menu");
+    opts.optflag("v", "version", "print the version");
+    let matches = opts
+        .parse(&args[1..])
+        .map_err(|e| {
+            eprintln!("Error: {}", e);
+            std::process::exit(64);
+        })
+        .unwrap();
+
+    if matches.opt_present("h") {
+        println!("{}", opts.usage(MAN_ION));
+        return;
     }
 
-    if stdin_is_a_tty {
+    if matches.opt_present("v") {
+        println!("{}", ion_shell::version());
+        return;
+    }
+
+    if matches.opt_present("n") {
+        shell.flags |= NO_EXEC;
+    }
+
+    let command = matches.opt_str("c");
+    let parameters = matches.free.into_iter().map(small::String::from).collect::<SmallVec<_>>();
+    let script_path = parameters.get(0).cloned();
+    if !parameters.is_empty() { shell.variables.set("args", parameters); }
+
+    let status = if let Some(command) = command {
+        shell.execute_script(&command);
+        shell.wait_for_background();
+        shell.previous_status
+    } else if let Some(path) = script_path {
+        shell.execute_file(&path.as_str());
+        shell.wait_for_background();
+        shell.previous_status
+    } else if stdin_is_a_tty {
         shell.execute_interactive();
+        unreachable!();
     } else {
         let reader = BufReader::new(stdin());
         let lines = reader.lines().filter_map(|line| line.ok());
-        let status = shell.terminate_script_quotes(lines);
-        shell.exit(status);
-    }
+        shell.terminate_script_quotes(lines)
+    };
+    shell.exit(status);
 }