···752752753753let word_components_to_string ws =
754754 String.concat "" (List.map word_component_to_string ws)
755755+756756+class check_ast =
757757+ object (_)
758758+ inherit [bool] Sast.fold
759759+ method int _ ctx = ctx
760760+ method string _ ctx = ctx
761761+ method char _ ctx = ctx
762762+ method option f v ctx = Option.fold ~none:ctx ~some:(fun i -> f i ctx) v
763763+ method nlist__t f v ctx = Nlist.fold_left (fun acc i -> f i acc) ctx v
764764+765765+ method nslist__t f g v ctx =
766766+ Nslist.fold_left (fun acc a b -> f a acc |> g b) ctx v
767767+768768+ method list f v ctx = List.fold_left (fun acc i -> f i acc) ctx v
769769+ end
770770+771771+let has_async ast =
772772+ let o =
773773+ object
774774+ inherit check_ast as super
775775+776776+ method! complete_command v ctx =
777777+ match v with
778778+ | _, Some Ampersand -> true
779779+ | _ -> super#complete_command v ctx
780780+781781+ method! separator_list f l ctx =
782782+ let v =
783783+ Nlist.to_list l
784784+ |> List.exists (function _, Ampersand -> true | _ -> false)
785785+ in
786786+ if v then v else super#separator_list f l ctx
787787+ end
788788+ in
789789+ o#complete_command ast false
+4
src/lib/ast.mli
···2222val word_component_to_string : word_component -> string
2323val word_components_to_string : word_cst -> string
24242525+val has_async : complete_command -> bool
2626+(** Checks, recursively, the command to see if there is any use of the async
2727+ operator [&] *)
2828+2529module Dump : sig
2630 val pp : t Fmt.t
2731 (** Dump the program *)
+38-11
src/lib/built_ins.ml
···11module Options = struct
22- type t = { noclobber : bool; pipefail : bool }
22+ type t = { noclobber : bool; pipefail : bool; async : bool }
3344- let default = { noclobber = false; pipefail = false }
44+ let default = { noclobber = false; pipefail = false; async = false }
5566- let with_options ?noclobber ?pipefail t =
66+ let with_options ?noclobber ?pipefail ?async t =
77 {
88 noclobber = Option.value ~default:t.noclobber noclobber;
99 pipefail = Option.value ~default:t.pipefail pipefail;
1010+ async = Option.value ~default:t.async async;
1011 }
11121212- type option = Noclobber | Pipefail
1313+ type posix = [ `Noclobber | `Pipefail ]
1414+ type merry = [ `Async ]
1515+ type option = [ posix | merry ]
13161417 let update t options =
1518 List.fold_left
1619 (fun d -> function
1717- | Pipefail -> with_options ~pipefail:true d
1818- | Noclobber -> with_options ~noclobber:true d)
2020+ | `Pipefail -> with_options ~pipefail:true d
2121+ | `Noclobber -> with_options ~noclobber:true d
2222+ | `Async -> with_options ~async:true d)
1923 t options
20242125 let pp ppf opt =
···2327 Fmt.pf ppf "%-12s %s@." name (if value then "on" else "off")
2428 in
2529 let opts =
2626- let { noclobber; pipefail } = opt in
2727- [ ("pipefail", pipefail); ("noclobber", noclobber) ]
3030+ let { noclobber; pipefail; async } = opt in
3131+ [ ("pipefail", pipefail); ("noclobber", noclobber); ("async", async) ]
2832 in
2933 Fmt.pf ppf "@[<v>%a@]" Fmt.(list pp_option) opts
3034end
···3236type set = { update : Options.option list; print_options : bool }
33373438(* Built-in Actions *)
3535-type t = Cd of { path : string option } | Pwd | Exit of int | Set of set
3939+type t =
4040+ | Cd of { path : string option }
4141+ | Pwd
4242+ | Exit of int
4343+ | Set of set
4444+ | Wait of int
36453746(* Change Directory *)
3847module Cd = struct
···86958796module Set = struct
8897 open Cmdliner
8989- open Options
90989191- let enum_map = [ ("pipefail", Pipefail); ("noclobber", Noclobber) ]
9999+ let enum_map =
100100+ [ ("pipefail", `Pipefail); ("noclobber", `Noclobber); ("async", `Async) ]
9210193102 let option =
94103 let doc = "Options." in
···104113 Cmd.v info term
105114end
106115116116+module Wait = struct
117117+ open Cmdliner
118118+119119+ let pid =
120120+ let doc = "Process ID." in
121121+ Arg.(value & pos 0 int 0 & info [] ~docv:"PID" ~doc)
122122+123123+ let t =
124124+ let make_wait n = Wait n in
125125+ let term = Term.(const make_wait $ pid) in
126126+ let info =
127127+ let doc = "Wait for a particular PID (default is 0)" in
128128+ Cmd.info "wait" ~doc
129129+ in
130130+ Cmd.v info term
131131+end
132132+107133let of_args (w : string list) =
108134 let open Cmdliner in
109135 let exec_cmd cmd v =
···119145 | "pwd" :: _ as cmd -> exec_cmd cmd Pwd.t
120146 | "exit" :: _ as cmd -> exec_cmd cmd Exit.t
121147 | "set" :: _ as cmd -> exec_cmd cmd Set.t
148148+ | "wait" :: _ as cmd -> exec_cmd cmd Wait.t
122149 | _ -> None
+6-3
src/lib/built_ins.mli
···11module Options : sig
22- type t = { noclobber : bool; pipefail : bool }
33- type option = Noclobber | Pipefail
22+ type t = { noclobber : bool; pipefail : bool; async : bool }
33+ type posix = [ `Noclobber | `Pipefail ]
44+ type merry = [ `Async ]
55+ type option = [ posix | merry ]
4657 val default : t
66- val with_options : ?noclobber:bool -> ?pipefail:bool -> t -> t
88+ val with_options : ?noclobber:bool -> ?pipefail:bool -> ?async:bool -> t -> t
79 val update : t -> option list -> t
810 val pp : t Fmt.t
911end
···1618 | Pwd
1719 | Exit of int
1820 | Set of set
2121+ | Wait of int
19222023val of_args : string list -> (t, string) result option
2124(** Parses a command-line to the built-ins, errors are returned if parsing. *)
+16-2
src/lib/eval.ml
···193193 in
194194 if print_options then Fmt.pr "%a%!" Built_ins.Options.pp ctx.options;
195195 v
196196+ | Wait i -> (
197197+ match Unix.waitpid [] i with
198198+ | _, WEXITED 0 -> Exit.zero ctx
199199+ | _, (WEXITED n | WSIGNALED n | WSTOPPED n) -> Exit.nonzero ctx n)
196200197201 let cwd_of_ctx ctx = S.cwd ctx.state |> Fpath.to_string |> ( / ) ctx.fs
198202···486490 | Ast.ForClause fc -> handle_for_clause ctx fc
487491 | Ast.IfClause if_ -> handle_if_clause ctx if_
488492 | _ as c ->
489489- Fmt.failwith "Compound command not supported: %a" yojson_pp
490490- (Ast.compound_command_to_yojson c)
493493+ Fmt.epr "Compound command not supported: %a\n%!" yojson_pp
494494+ (Ast.compound_command_to_yojson c);
495495+ exit 127
491496492497 and needs_subshelling = function
493498 | [] -> false
···608613 | [] -> (ctx, cs)
609614 | command :: commands -> (
610615 let ctx = Exit.value ctx in
616616+ (* For our sanity *)
617617+ let has_async = Ast.has_async command in
618618+ if has_async && not ctx.options.async then begin
619619+ Fmt.epr
620620+ "You are using asynchronous operators and [set -o async] has \
621621+ not been called.\n\
622622+ %!";
623623+ exit 1
624624+ end;
611625 let exit = execute ctx command in
612626 match exit with
613627 | Exit.Nonzero { exit_code; message; should_exit; _ } -> (
+23
test/async.t
···11+Asynchronous jobs are tricky. For now, we disable them behind a flag in [set].
22+33+ $ osh -c "echo hello &"
44+ You are using asynchronous operators and [set -o async] has not been called.
55+ [1]
66+77+But we do have some support for them.
88+99+ $ cat > osh.sh << EOF
1010+ > set -o async
1111+ > sleep 10000 & echo hello
1212+ > kill -9 \$!
1313+ > EOF
1414+1515+ $ cat > test.sh << EOF
1616+ > sleep 10000 & echo hello
1717+ > kill -9 \$!
1818+ > EOF
1919+2020+ $ sh test.sh
2121+ hello
2222+ $ osh osh.sh
2323+ hello