diff --git a/src/lib/built_ins.ml b/src/lib/built_ins.ml index d2cb40a..686bbe3 100644 --- a/src/lib/built_ins.ml +++ b/src/lib/built_ins.ml @@ -36,8 +36,8 @@ end type set = { update : Options.option list; print_options : bool } type hash = Hash_remove | Hash_stats | Hash_add of string list -(* Built-in Actions *) type t = + (* Built-in Actions *) | Cd of { path : string option } | Pwd | Exit of int @@ -46,6 +46,7 @@ type t = | Dot of string (* a.k.a source *) | Unset of [ `Variables of string list | `Functions of string list ] | Hash of hash + | Command of string list (* Change Directory *) module Cd = struct @@ -191,6 +192,23 @@ module Hash = struct Cmd.v info term end +module Command = struct + open Cmdliner + + let args = + let doc = "Arguments to command." in + Arg.(value & pos_all string [] & info [] ~docv:"ARGS" ~doc) + + let t = + let make_command pos_all = Command pos_all in + let term = Term.(const make_command $ args) in + let info = + let doc = "Execute a simple command." in + Cmd.info "command" ~doc + in + Cmd.v info term +end + module Make_dot (T : sig val name : string end) = @@ -239,4 +257,5 @@ let of_args (w : string list) = | "." :: _ as cmd -> exec_cmd cmd Dot.t | "unset" :: _ as cmd -> exec_cmd cmd Unset.t | "hash" :: _ as cmd -> exec_cmd cmd Hash.t + | "command" :: _ as cmd -> exec_cmd cmd Command.t | _ -> None diff --git a/src/lib/built_ins.mli b/src/lib/built_ins.mli index d8892ff..18c1177 100644 --- a/src/lib/built_ins.mli +++ b/src/lib/built_ins.mli @@ -23,6 +23,7 @@ type t = | Dot of string | Unset of [ `Variables of string list | `Functions of string list ] | Hash of hash + | Command of string list val of_args : string list -> (t, string) result option (** Parses a command-line to the built-ins, errors are returned if parsing. *) diff --git a/src/lib/eval.ml b/src/lib/eval.ml index be5efa9..02fc696 100644 --- a/src/lib/eval.ml +++ b/src/lib/eval.ml @@ -291,15 +291,12 @@ module Make (S : Types.State) (E : Types.Exec) = struct |> List.rev in match Built_ins.of_args (executable :: args_as_strings) with - | Some (Ok bi) -> - let ctx = handle_built_in ~rdrs ~stdout:some_write ctx bi in - close_stdout ~is_global some_write; - let built_in = ctx >|= fun _ -> () in - let job = handle_job ~pgid job (`Built_in built_in) in - loop (Exit.value ctx) job (pgid, some_read) rest | Some (Error _) -> (ctx, handle_job ~pgid job (`Built_in (Exit.nonzero () 1))) - | None -> ( + | (None | Some (Ok (Command _))) as v -> ( + let is_command = + match v with Some (Ok (Command _)) -> true | _ -> false + in (* We handle the [export] built_in explicitly as we need access to the raw CST *) match executable with @@ -311,11 +308,14 @@ module Make (S : Types.State) (E : Types.Exec) = struct loop (Exit.value updated) job (pgid, stdout_of_previous) rest | _ -> ( let saved_ctx = ctx in - match - let ctx = { ctx with stdout = some_write } in - handle_function_application ctx ~name:executable - (ctx.program :: args_as_strings) - with + let func_app = + if is_command then None + else + let ctx = { ctx with stdout = some_write } in + handle_function_application ctx ~name:executable + (ctx.program :: args_as_strings) + in + match func_app with | Some ctx -> close_stdout ~is_global some_write; (* TODO: Proper job stuff and redirects etc. *) @@ -324,11 +324,20 @@ module Make (S : Types.State) (E : Types.Exec) = struct in loop saved_ctx job (pgid, some_read) rest | None -> ( + let executable, args = + if is_command then begin + match args_as_strings with + | [] -> ("", []) + | [ x ] -> (x, []) + | x :: xs -> (x, xs) + end + else (executable, args_as_strings) + in match stdout_of_previous with | None -> let ctx, job = exec_process ctx job ~fds:rdrs ~stdout:some_write - ~pgid executable args_as_strings + ~pgid executable args in close_stdout ~is_global some_write; loop ctx job (pgid, some_read) rest @@ -339,7 +348,13 @@ module Make (S : Types.State) (E : Types.Exec) = struct args_as_strings in close_stdout ~is_global some_write; - loop ctx job (pgid, some_read) rest)))) + loop ctx job (pgid, some_read) rest))) + | Some (Ok bi) -> + let ctx = handle_built_in ~rdrs ~stdout:some_write ctx bi in + close_stdout ~is_global some_write; + let built_in = ctx >|= fun _ -> () in + let job = handle_job ~pgid job (`Built_in built_in) in + loop (Exit.value ctx) job (pgid, some_read) rest) | CompoundCommand (c, rdrs) :: rest -> let _rdrs = List.map (handle_one_redirection ~sw:pipeline_switch ctx) rdrs @@ -636,6 +651,10 @@ module Make (S : Types.State) (E : Types.Exec) = struct | Ast.ForClause fc -> handle_for_clause ctx fc | Ast.IfClause if_ -> handle_if_clause ctx if_ | Ast.BraceGroup (term, sep) -> exec ctx (term, Some sep) + | Ast.Subshell (term, sep) -> + let saved_ctx = ctx in + let e = exec ctx (term, Some sep) in + e >|= fun _ -> saved_ctx | _ as c -> Fmt.epr "Compound command not supported: %a\n%!" yojson_pp (Ast.compound_command_to_yojson c); @@ -807,6 +826,9 @@ module Make (S : Types.State) (E : Types.Exec) = struct Eio.Flow.copy_string (Fmt.str "%a" Hash.pp ctx.hash) stdout; Exit.zero ctx | _ -> assert false) + | Command _ -> + (* Handled separately *) + assert false and exec initial_ctx ((command, sep) : Ast.complete_command) = let rec loop : Eio.Switch.t -> ctx -> Ast.clist -> ctx Exit.t = diff --git a/test/built_ins.t b/test/built_ins.t index b2b3dda..9b84f54 100644 --- a/test/built_ins.t +++ b/test/built_ins.t @@ -96,6 +96,9 @@ Testing some shell built-ins 5. Hash +First clean up the directory a bit + $ rm *.sh + $ cat > test.sh << EOF > reproducible_hash () { > hash | sed 's/|/ /' | awk '{print \$1, \$8}' @@ -109,34 +112,23 @@ Testing some shell built-ins $ sh test.sh hello.txt - run.sh test.sh - test_bad.sh - test_good.sh testing hello.txt - run.sh test.sh - test_bad.sh - test_good.sh testing hits 2 $ msh test.sh hello.txt - run.sh test.sh - test_bad.sh - test_good.sh testing hello.txt - run.sh test.sh - test_bad.sh - test_good.sh testing hits 2 + 6. Built-in redirection and pipelining $ sh -c "FOO=\$(pwd | rev | rev); echo \$(basename \$FOO)" @@ -148,3 +140,37 @@ Testing some shell built-ins test $ msh -c "pwd > out.txt; cat out.txt | xargs -- basename" test + +7. Command + + $ sh -c "command ls" + hello.txt + out.txt + test.sh + testing + $ msh -c "command ls" + hello.txt + out.txt + test.sh + testing + + $ cat > test.sh << EOF + > ls () { + > echo "Fake LS!" + > } + > ls + > command ls + > EOF + + $ sh test.sh + Fake LS! + hello.txt + out.txt + test.sh + testing + $ msh test.sh + Fake LS! + hello.txt + out.txt + test.sh + testing diff --git a/test/subshell.t b/test/subshell.t index 2eaeae4..1c929f1 100644 --- a/test/subshell.t +++ b/test/subshell.t @@ -12,3 +12,4 @@ A simple test. hello $ msh -c "`which echo` hello" hello +