···379379end
380380381381module Eval = struct
382382- open Cmdliner
383383-384384- let args =
385385- let doc = "Arguments to concatenate, parse and execute." in
386386- Arg.(value & pos_all string [] & info [] ~docv:"ARGS" ~doc)
387387-388388- let t =
389389- let make_eval args = Eval args in
390390- let term = Term.(const make_eval $ args) in
391391- let info =
392392- let doc = "Construct a command by concatenating arguments together." in
393393- Cmd.info "eval" ~doc
394394- in
395395- Cmd.v info term
382382+ let of_args w = Some (Ok (Eval w))
396383end
397384398385module Echo = struct
···600587 | "command" :: cmd -> Command.of_strings cmd
601588 | "alias" :: _ -> Some (Ok Alias)
602589 | "unalias" :: _ -> Some (Ok Unalias)
603603- | "eval" :: _ as cmd -> exec_cmd cmd Eval.t
590590+ | "eval" :: cmd -> Eval.of_args cmd
604591 | "echo" :: cmd -> Echo.of_args cmd
605592 | "trap" :: _ as cmd -> exec_cmd cmd Trap.t
606593 | "return" :: _ as cmd -> exec_cmd cmd Return.t
+5
src/lib/built_ins.mli
···62626363val of_args : string list -> (t, string) result option
6464(** Parses a command-line to the built-ins, errors are returned if parsing. *)
6565+6666+(* To be shared with shell binary CLIs *)
6767+module Set : sig
6868+ val errexit : bool Cmdliner.Term.t
6969+end
+4-4
src/lib/eunix.ml
···6363let fd_of_int (fd : int) : Unix.file_descr = Obj.magic fd
64646565let with_redirections ?(restore = false) (rdrs : Types.redirect list) fn =
6666- let saved_stdin = Unix.dup Unix.stdin in
6767- let saved_stdout = Unix.dup Unix.stdout in
6868- let saved_stderr = Unix.dup Unix.stderr in
6666+ let saved_stdin = Safe_fd.dup Unix.stdin in
6767+ let saved_stdout = Safe_fd.dup Unix.stdout in
6868+ let saved_stderr = Safe_fd.dup Unix.stderr in
6969 let restore_fds =
7070 List.filter_map
7171 (function
···7474 let new_fd = fd_of_int i in
7575 if (Obj.magic fd : int) <> i then begin
7676 let saved_fd =
7777- try Some (Unix.dup new_fd)
7777+ try Some (Safe_fd.dup new_fd)
7878 with Unix.Unix_error (Unix.EBADF, _, _) -> None
7979 in
8080 Unix.dup2 ~cloexec:false fd new_fd;
+144-64
src/lib/eval.ml
···66open Import
77open Exit.Syntax
8899+let mk_new_id =
1010+ let i = ref 0 in
1111+ fun () ->
1212+ incr i;
1313+ "id" ^ string_of_int @@ !i
1414+1515+let mk_pipeline_scope () = "pipeline-" ^ mk_new_id ()
916let pp_args = Fmt.(list ~sep:(Fmt.any " ") string)
10171118let pp_fs_create ppf (v : Eio.Fs.create) =
···5663 exit_handler : (unit -> unit) option;
5764 in_double_quotes : bool;
5865 umask : int;
5959- fd_pool : Fd_pool.t;
6666+ current_pipeline : string option;
6067 }
61686269 exception Continue of int * ctx
···6976 (* Used for the [return] non-POSIX keyword *)
70777178 let make_ctx ?(interactive = false) ?(subshell = false) ?(local_state = [])
7272- ?(background_jobs = []) ?(last_background_process = "")
7979+ ?(background_jobs = []) ?(last_background_process = "") ?current_pipeline
7380 ?last_pipeline_status ?(functions = []) ?(rdrs = []) ?exit_handler
7481 ?(options = Built_ins.Options.default) ?(hash = Hash.empty)
7582 ?(in_double_quotes = false) ?(umask = 0o22) ~fs ~stdin ~stdout
7683 ~async_switch ~program ~argv ~signal_handler state executor =
7784 let signal_handler = { run = signal_handler; sigint_set = false } in
8585+ let state = S.update state ~param:"IFS" " \t\n" |> Result.get_ok in
7886 {
7987 interactive;
8088 subshell;
···98106 exit_handler;
99107 in_double_quotes;
100108 umask;
101101- fd_pool = Fd_pool.make 256;
109109+ current_pipeline;
102110 }
103111104112 let state ctx = ctx.state
···106114 let fs ctx = ctx.fs
107115 let clear_local_state ctx = { ctx with local_state = [] }
108116117117+ let with_pipeline_scope ?(force = false) ?(remove_vars = true) ctx fn =
118118+ let saved_pipeline = ctx.current_pipeline in
119119+ let current_pipeline =
120120+ if force then mk_pipeline_scope ()
121121+ else Option.value ~default:(mk_pipeline_scope ()) ctx.current_pipeline
122122+ in
123123+ let changed = force || Option.is_none ctx.current_pipeline in
124124+ let v = fn { ctx with current_pipeline = Some current_pipeline } in
125125+ Exit.map
126126+ ~f:(fun c ->
127127+ {
128128+ c with
129129+ current_pipeline = saved_pipeline;
130130+ state =
131131+ (if changed && remove_vars then
132132+ S.remove_group ~id:current_pipeline c.state
133133+ else c.state);
134134+ })
135135+ v
136136+109137 let tilde_expansion ctx = function
110138 | Ast.WordTildePrefix _ -> Ast.WordTildePrefix (S.expand ctx.state `Tilde)
111139 | v -> v
···122150 let stdout_for_pipeline ~sw ctx = function
123151 | [] -> (None, `Global ctx.stdout)
124152 | _ ->
125125- let r, w = Fd_pool.pipe ctx.fd_pool sw in
153153+ let r, w = Safe_fd.pipe sw in
126154 (Some r, `Local (w :> Eio_unix.sink_ty Eio.Flow.sink))
127155128156 let fd_of_int ?(close_unix = true) ~sw (n : int) =
···199227 if not is_global then begin
200228 Eio.Flow.close some_write
201229 end
230230+ in
231231+ let update_stdin ~stdin ctx =
232232+ { ctx with stdin = Option.value ~default:ctx.stdin stdin }
202233 in
203234 let exec_process ~sw ctx job ?fds ?stdin ~stdout ?pgid executable args =
204235 let pgid = match pgid with None -> 0 | Some p -> p in
···221252 fds);
222253 ( ctx,
223254 E.exec ctx.executor ~delay_reap:(fst reap) ~fds ?stdin ~stdout
224224- ~pgid ~mode ~cwd:(cwd_of_ctx ctx)
255255+ ~pgid ~mode ~cwd:(cwd_of_ctx ctx) ~pipe:Safe_fd.pipe
225256 ~env:(get_env ~extra:ctx.local_state ctx)
226257 ~executable:full_path (executable :: args) )
227258 in
···235266 (on_process ~async ~process ctx, job)
236267 in
237268 let job_pgid (t : J.t) = J.get_id t in
238238- let rec loop pipeline_switch (ctx : ctx) (job : J.t)
239239- (stdout_of_previous : Eio_unix.source_ty Eio_unix.source option) :
269269+ let rec loop pipeline_switch (ctx : ctx) (job : J.t) :
240270 Ast.command list -> ctx * J.t =
241271 fun c ->
242272 let loop = loop pipeline_switch in
···247277 (Ast.cmd_prefix_to_yojson prefix));
248278 let ctx = collect_assignments ctx prefix in
249279 let job = handle_job job (`Built_in (Exit.ignore ctx)) in
250250- loop (Exit.value ctx) job stdout_of_previous rest
280280+ loop (Exit.value ctx) job rest
251281 | Ast.SimpleCommand (Prefixed (prefix, Some executable, suffix)) :: rest
252282 ->
253283 let ctx = collect_assignments ~update:false ctx prefix in
254284 let job = handle_job job (`Built_in (Exit.ignore ctx)) in
255255- loop (Exit.value ctx) job stdout_of_previous
285285+ loop (Exit.value ctx) job
256286 (Ast.SimpleCommand (Named (executable, suffix)) :: rest)
257287 | Ast.SimpleCommand (Named (executable, suffix)) :: rest -> (
258288 let ctx, executable = word_expansion ctx executable in
259289 match ctx with
260290 | Exit.Nonzero _ as ctx ->
261291 let job = handle_job job (`Built_in (Exit.ignore ctx)) in
262262- loop (Exit.value ctx) job stdout_of_previous rest
292292+ loop (Exit.value ctx) job rest
263293 | Exit.Zero ctx -> (
264294 let executable, extra_args =
265295 (* This is a side-effect of the alias command with something like
···286316 match ctx with
287317 | Exit.Nonzero _ as ctx ->
288318 let job = handle_job job (`Built_in (Exit.ignore ctx)) in
289289- loop (Exit.value ctx) job stdout_of_previous rest
319319+ loop (Exit.value ctx) job rest
290320 | Exit.Zero ctx -> (
291321 let some_read, some_write =
292322 stdout_for_pipeline ~sw:pipeline_switch ctx rest
···336366 in
337367 Debug.Log.debug (fun f ->
338368 f "export %a" pp_args args);
339339- loop (Exit.value updated) job stdout_of_previous
340340- rest
369369+ loop (Exit.value updated) job rest
341370 | "readonly" ->
342371 let updated =
343372 handle_assignments `Readonly ctx args
···348377 in
349378 Debug.Log.debug (fun f ->
350379 f "readonly %a" pp_args args);
351351- loop (Exit.value updated) job stdout_of_previous
352352- rest
380380+ loop (Exit.value updated) job rest
353381 | "local" ->
354382 let updated =
355383 handle_assignments `Local ctx args
···358386 handle_job job
359387 (`Built_in (updated >|= fun _ -> ()))
360388 in
361361- loop (Exit.value updated) job stdout_of_previous
362362- rest
389389+ loop (Exit.value updated) job rest
363390 | "exec" ->
364364- (* let _ = Sys.command "ls -la /proc/self/fd" in *)
365391 Debug.Log.debug (fun f ->
366392 f "exec [%a] [%a]" pp_args args
367393 Fmt.(list Types.pp_redirect)
···370396 Eunix.with_redirections ~restore:false rdrs
371397 @@ fun () ->
372398 if args <> [] then
373373- Fmt.invalid_arg
374374- "Exec with args not yet supported...";
375375- (ctx, job)
399399+ let name = List.hd args in
400400+ let prog =
401401+ match
402402+ resolve_program ~update:false ctx name
403403+ with
404404+ | _, None -> Fmt.failwith "%s not found" name
405405+ | _, Some p -> p
406406+ in
407407+ Unix.execve prog (Array.of_list args)
408408+ (Array.of_list
409409+ @@ List.map (fun (k, v) -> k ^ "=" ^ v)
410410+ @@ get_env ~extra:ctx.local_state ctx)
411411+ else (ctx, job)
376412 | ":" -> (ctx, job)
377413 | _ -> (
378414 let saved_ctx = ctx in
···393429 loop
394430 {
395431 saved_ctx with
432432+ stdin =
433433+ Option.value ~default:saved_ctx.stdin
434434+ some_read;
396435 state = (Exit.value ctx).state;
397436 }
398398- job some_read rest
437437+ job rest
399438 | None -> (
400439 match Built_ins.of_args command_args with
401440 | Some (Error _) ->
···427466 handle_job job
428467 (`Built_in (Exit.ignore ctx))
429468 in
430430- loop (Exit.value ctx) job some_read rest
469469+ let ctx =
470470+ Exit.map
471471+ ~f:(update_stdin ~stdin:some_read)
472472+ ctx
473473+ in
474474+ loop (Exit.value ctx) job rest
431475 | _ -> (
432476 let exec_and_args =
433477 if is_command then begin
···453497 handle_job job
454498 (`Built_in (Exit.ignore v))
455499 in
456456- loop ctx job some_read rest
457457- | Exit.Zero (executable, args) -> (
458458- match stdout_of_previous with
459459- | None ->
460460- let ctx, job =
461461- exec_process ~sw:pipeline_switch
462462- ctx job ~fds:rdrs
463463- ~stdout:some_write
464464- ~pgid:(job_pgid job)
465465- executable args
466466- in
467467- close_stdout ~is_global some_write;
468468- loop ctx job some_read rest
469469- | Some stdout ->
470470- let ctx, job =
471471- exec_process ~sw:pipeline_switch
472472- ctx job ~fds:rdrs
473473- ~stdin:stdout
474474- ~stdout:some_write
475475- ~pgid:(job_pgid job)
476476- executable args
477477- in
478478- close_stdout ~is_global some_write;
479479- loop ctx job some_read rest)))))
500500+ let ctx =
501501+ update_stdin ~stdin:some_read ctx
502502+ in
503503+ loop ctx job rest
504504+ | Exit.Zero (executable, args) ->
505505+ let ctx, job =
506506+ exec_process ~sw:pipeline_switch ctx
507507+ job ~fds:rdrs ~stdin:ctx.stdin
508508+ ~stdout:some_write
509509+ ~pgid:(job_pgid job) executable
510510+ args
511511+ in
512512+ close_stdout ~is_global some_write;
513513+ let ctx =
514514+ update_stdin ~stdin:some_read ctx
515515+ in
516516+ loop ctx job rest))))
480517 | Some (Ok bi) ->
481518 let rdrs = make_child_rdrs_for_parent rdrs in
482519 let ctx =
···496533 else handle_job job (`Exit (Exit.ignore ctx))
497534 | _ -> handle_job job (`Built_in (Exit.ignore ctx))
498535 in
499499- loop (Exit.value ctx) job some_read rest))))
536536+ let ctx =
537537+ Exit.map ~f:(update_stdin ~stdin:some_read) ctx
538538+ in
539539+ loop (Exit.value ctx) job rest))))
500540 | CompoundCommand (c, rdrs) :: rest -> (
541541+ let some_read, some_write =
542542+ stdout_for_pipeline ~sw:pipeline_switch ctx rest
543543+ in
544544+ let is_global, some_write =
545545+ match some_write with
546546+ | `Global p -> (true, p)
547547+ | `Local p -> (false, p)
548548+ in
501549 match handle_redirections ~sw:pipeline_switch ctx rdrs with
502550 | Error ctx -> (ctx, handle_job job (`Rdr (Exit.nonzero () 1)))
503551 | Ok rdrs ->
504552 let saved_rdrs = ctx.rdrs in
553553+ let saved_stdout = ctx.stdout in
505554 let rdrs = make_child_rdrs_for_parent rdrs in
506555 (* TODO: No way this is right *)
507507- let ctx = { ctx with rdrs = rdrs @ saved_rdrs } in
508508- let ctx = handle_compound_command ctx c in
556556+ let ctx =
557557+ { ctx with rdrs = rdrs @ saved_rdrs; stdout = some_write }
558558+ in
559559+ let ctx =
560560+ with_pipeline_scope ctx @@ fun ctx ->
561561+ handle_compound_command ctx c
562562+ in
563563+ close_stdout ~is_global some_write;
509564 let job = handle_job job (`Built_in (ctx >|= fun _ -> ())) in
510565 let actual_ctx = Exit.value ctx in
511511- loop { actual_ctx with rdrs = saved_rdrs } job None rest)
566566+ loop
567567+ {
568568+ actual_ctx with
569569+ rdrs = saved_rdrs;
570570+ stdin = Option.value ~default:actual_ctx.stdin some_read;
571571+ stdout = saved_stdout;
572572+ }
573573+ job rest)
512574 | FunctionDefinition (name, (body, _rdrs)) :: rest ->
513575 let ctx = { ctx with functions = (name, body) :: ctx.functions } in
514514- loop ctx job None rest
576576+ loop ctx job rest
515577 | [] -> (clear_local_state ctx, job)
516578 in
517579 Eio.Switch.run @@ fun sw ->
···519581 let saved_ctx = initial_ctx in
520582 let subshell = saved_ctx.subshell || List.length p > 1 in
521583 let ctx = { initial_ctx with subshell } in
522522- let ctx, job = loop sw ctx initial_job None p in
584584+ with_pipeline_scope ctx @@ fun ctx ->
585585+ let ctx, job = loop sw ctx initial_job p in
586586+ let ctx = { ctx with stdin = saved_ctx.stdin; state = ctx.state } in
523587 match J.size job with
524588 | 0 -> Exit.zero ctx
525589 | _ ->
···614678 | Ast.IoRedirect_IoHere (i, Ast.IoHere (_, v)) ->
615679 let _ctx, cst = word_expansion ctx v in
616680 let s = List.concat cst |> Ast.Fragment.join_list ~sep:"" in
617617- let r, w = Fd_pool.pipe ctx.fd_pool sw in
681681+ let r, w = Safe_fd.pipe sw in
618682 Eio.Flow.copy_string s w;
619683 Eio.Flow.close w;
620684 let fd = Eio_unix.Resource.fd_opt r |> Option.get in
···632696 List.concat cst |> List.map strip_tab
633697 |> Ast.Fragment.join_list ~sep:""
634698 in
635635- let r, w = Fd_pool.pipe ctx.fd_pool sw in
699699+ let r, w = Safe_fd.pipe sw in
636700 Eio.Flow.copy_string s w;
637701 Eio.Flow.close w;
638702 let fd = Eio_unix.Resource.fd_opt r |> Option.get in
···681745 match int_of_string_opt param with
682746 | Some n -> (
683747 match Array.get ctx.argv n with
684684- | v -> Some v
748748+ | v ->
749749+ Debug.Log.debug (fun f ->
750750+ f "lookup %s => %a" param Fmt.(quote string) v);
751751+ Some v
685752 | exception Invalid_argument _ -> None)
686686- | None -> S.lookup ctx.state ~param
753753+ | None ->
754754+ let v = S.lookup ctx.state ~param in
755755+ Debug.Log.debug (fun f ->
756756+ f "lookup %s => %a" param Fmt.(quote (option string)) v);
757757+ v
687758 in
688759 let expand ctx v : ctx Exit.t * Ast.fragment list list =
689760 let module Fragment = struct
···914985 | Ast.WordSubshell sub ->
915986 (* Command substitution *)
916987 let s = command_substitution ctx sub in
917917- (Exit.zero ctx, [ [ Fragment.make s ] ])
988988+ (Exit.zero ctx, [ [ Fragment.make ~join:`Yes s ] ])
918989 | Ast.WordArithmeticExpression cst ->
919990 arithmetic_expansion ctx cst |> fun (ctx, v) ->
920991 (Exit.zero ctx, [ [ v ] ])
···1196126711971268 and exec_subshell ctx (term, sep) =
11981269 let saved_ctx = ctx in
12701270+ Debug.Log.debug (fun f -> f "enter subshell");
11991271 let e = exec ctx (term, Some sep) in
12721272+ Debug.Log.debug (fun f -> f "leave subshell");
12001273 let v = e >|= fun _ -> saved_ctx in
12011274 v
12021275···12481321 | Some commands ->
12491322 Debug.Log.debug (fun f ->
12501323 f "function enter: %s [%a]" name
12511251- Fmt.(list ~sep:Fmt.(any " ") string)
13241324+ Fmt.(list ~sep:Fmt.(any " ") (quote string))
12521325 argv);
12531326 let ctx = { ctx with argv = Array.of_list argv } in
12541327 let v =
···12641337 let stdout = Eio.Flow.buffer_sink buf in
12651338 let sub_ctx =
12661339 Eio.Switch.run @@ fun sw ->
12671267- let r, w = Fd_pool.pipe ctx.fd_pool sw in
13401340+ let r, w = Safe_fd.pipe sw in
12681341 Eio.Fiber.fork ~sw (fun () -> Eio.Flow.copy r stdout);
12691342 let subshell_ctx = { ctx with stdout = w; subshell = true } in
12701343 let sub_ctx, _ = run (Exit.zero subshell_ctx) s in
···13071380 match ctx with
13081381 | Exit.Nonzero _ as ctx -> ctx
13091382 | Exit.Zero ctx -> (
13831383+ let s = Ast.Fragment.join_list ~sep:"" @@ List.concat v in
13841384+ Debug.Log.debug (fun f ->
13851385+ f "collect assignment: %s is %a : %i chars" param
13861386+ Fmt.(quote string)
13871387+ s (String.length s));
13101388 let state =
13111389 (* TODO: Overhaul... need to collect assignments after word expansion...*)
13121390 if update || String.equal "IFS" param then
···14391517 | _ -> assert false)
14401518 | Alias | Unalias -> Exit.zero ctx (* Morbig handles this for us *)
14411519 | Eval args ->
14421442- let script = String.concat "" args in
15201520+ let script = String.concat " " args in
14431521 let ast = Ast.of_string script in
14441522 let ctx, _ = run (Exit.zero ctx) ast in
14451523 ctx
14461524 | Echo args ->
14471447- let str = String.concat " " (List.map String.trim args) ^ "\n" in
15251525+ let str = String.concat " " args ^ "\n" in
14481526 Eio.Flow.copy_string str stdout;
14491527 Exit.zero ctx
14501528 | Trap (action, signals) ->
···15291607 | [], lines ->
15301608 let last_var, last_line = List.hd acc in
15311609 List.rev
15321532- ((last_var, last_line ^ Ast.Fragment.join_list ~sep:"" lines)
16101610+ ((last_var, last_line ^ Ast.Fragment.join_list ~sep:" " lines)
15331611 :: acc)
15341612 in
15351613 let fields =
···15441622 let vars = loop [] (vars, fs) in
15451623 let state =
15461624 List.fold_left
15471547- (fun st (k, v) -> S.update st ~param:k v |> Result.get_ok)
16251625+ (fun st (k, v) ->
16261626+ S.update ?id:ctx.current_pipeline st ~param:k v
16271627+ |> Result.get_ok)
15481628 ctx.state vars
15491629 in
15501630 Exit.zero { ctx with state })
+1
src/lib/eval.mli
···1616 ?local_state:(string * string) list ->
1717 ?background_jobs:J.t list ->
1818 ?last_background_process:string ->
1919+ ?current_pipeline:string ->
1920 ?last_pipeline_status:int ->
2021 ?functions:(string * Sast.compound_command) list ->
2122 ?rdrs:Types.redirect list ->
-43
src/lib/fd_pool.ml
···11-type t = { pool : (Unix.file_descr * bool) array }
22-33-external is_actually_free : Unix.file_descr -> bool = "caml_merry_is_fd_free"
44-55-let make ?(min = 200) size =
66- let pool =
77- Array.init size (fun i -> ((Obj.magic (i + min) : Unix.file_descr), true))
88- in
99- { pool }
1010-1111-let next_free t = Array.find_index (fun (_, free) -> free) t.pool
1212-1313-let get_fd ~sw t =
1414- match next_free t with
1515- | None -> Fmt.failwith "No free FDs"
1616- | Some idx ->
1717- let fd, _ = Array.get t.pool idx in
1818- Eio.Switch.on_release sw (fun () -> Array.set t.pool idx (fd, true));
1919- Array.set t.pool idx (fd, false);
2020- fd
2121-2222-let with_fd t fn =
2323- Eio.Switch.run @@ fun sw ->
2424- let fd = get_fd ~sw t in
2525- assert (is_actually_free fd);
2626- fn fd
2727-2828-let pipe t sw =
2929- let r, w = Eio_unix.pipe sw in
3030- let r_fd, w_fd = (Eio_unix.Resource.fd r, Eio_unix.Resource.fd w) in
3131- let new_r_fd, new_w_fd =
3232- Eio_unix.Fd.use_exn "pipe" r_fd @@ fun r_fd ->
3333- Eio_unix.Fd.use_exn "pipe" w_fd @@ fun w_fd ->
3434- let new_r_fd = get_fd ~sw t in
3535- let new_w_fd = get_fd ~sw t in
3636- Unix.dup2 ~cloexec:true r_fd new_r_fd;
3737- Unix.dup2 ~cloexec:true w_fd new_w_fd;
3838- (new_r_fd, new_w_fd)
3939- in
4040- Eio_unix.Fd.close r_fd;
4141- Eio_unix.Fd.close w_fd;
4242- ( Eio_posix.Flow.of_fd (Eio_unix.Fd.of_unix ~close_unix:true ~sw new_r_fd),
4343- Eio_posix.Flow.of_fd (Eio_unix.Fd.of_unix ~close_unix:true ~sw new_w_fd) )
-17
src/lib/fd_pool.mli
···11-(* A simple file descriptor pool to deal with overwriting low
22- file descriptor by mistake that might be our own pipe's and
33- stuff! *)
44-55-type t
66-(** A file descriptor pool *)
77-88-val make : ?min:int -> int -> t
99-(** [make ?min size] creates a new pool of [size] where the lowest FD is [min].
1010-*)
1111-1212-val with_fd : t -> (Unix.file_descr -> 'a) -> 'a
1313-(** [with_fd pool fn] runs [fn] with the next available file descriptor, when
1414- the function returns the [fd] is returned to the pool to be reused. *)
1515-1616-val pipe :
1717- t -> Eio.Switch.t -> Eio_unix.source_ty Eio.Std.r * Eio_unix.sink_ty Eio.Std.r
···11+(** This module "safely" provides file descriptors for the shell. In general,
22+ these need to be opened above 10 to avoid accidentally clashing with
33+ frequent shellisms like [exec 4>&1]. *)
44+55+val dup : Unix.file_descr -> Unix.file_descr
66+(** Like {! Unix.dup} but guarantees the file descriptor is opened above [10].
77+*)
88+99+val pipe :
1010+ Eio.Switch.t -> Eio_unix.source_ty Eio.Std.r * Eio_unix.sink_ty Eio.Std.r
1111+(** Like {! Eio_unix.pipe} except it guarantees that the FD is above [10]. *)
+2-1
src/lib/sast.ml
···127127 splittable : bool;
128128 globbable : bool;
129129 tilde_expansion : bool;
130130- join : [ `No | `With_previous | `With_next ]; (* Used for "args: [$@]" *)
130130+ join : [ `No | `With_previous | `With_next | `Yes ];
131131+ (* Used for "args: [$@]" *)
131132}
132133(** Post expansion representation of strings ready for possible field splitting
133134 and globbing. *)
+11-1
src/lib/types.ml
···2424 (** Parameter lookup. [None] means [unset]. *)
25252626 val update :
2727+ ?id:string ->
2728 ?export:bool ->
2829 ?readonly:bool ->
2930 t ->
···3233 (t, string) result
3334 (** Update the state with a new parameter mapping and whether or not it should
3435 exported to the environment (default false), if it is readonly (default
3535- false). *)
3636+ false).
3737+3838+ [id] can be used to group variables together (for example, all of the
3939+ variable that belong to a particular function call or pipeline). *)
36403741 val remove : param:string -> t -> bool * t
3842 (** [remove ~param t] removes [param] from [t] if it exists. [bool] is [true]
3943 if a removal took place. *)
4444+4545+ val remove_group : id:string -> t -> t
4646+ (** [remove_group ~id t] removes all variables with the [id] group. *)
40474148 val exports : t -> (string * string) list
4249 (** All of the variables that must be exported to the environment *)
···9198 mode:exec_mode ->
9299 pgid:int ->
93100 cwd:Eio.Fs.dir_ty Eio.Path.t ->
101101+ pipe:
102102+ (Eio.Switch.t ->
103103+ Eio_unix.source_ty Eio.Std.r * Eio_unix.sink_ty Eio.Std.r) ->
94104 executable:string ->
95105 t ->
96106 string list ->
···3434 Line
3535 Line /bin/sh
3636 $ msh test.sh
3737- Line
3737+ Line
3838 Line /bin/sh
+1-1
test/options.t
···2828 test.sh: line 3: UNSETVAR: unbound variable
2929 [1]
3030 $ msh test.sh
3131- The variable is:
3131+ The variable is:
3232 UNSETVAR: unbound variable
3333 [1]
+14
test/random.t
···2222 0
2323 --perl-regexp
24242525+Another example from the Debian world, this time when installing the
2626+CA-certificates, the main idea is testing that piping works whenever their are
2727+compound commands thrown into the mixed.
2828+2929+ $ cat > test.sh << EOF
3030+ > VAR="world,there,hello"
3131+ > vars=\$( (echo "\$VAR" | tr ',' ' ') | rev)
3232+ > echo "Vars are \$vars"
3333+ > EOF
3434+3535+ $ sh test.sh
3636+ Vars are olleh ereht dlrow
3737+ $ msh test.sh
3838+ Vars are olleh ereht dlrow