···11module Options = struct
22- type t = { noclobber : bool; pipefail : bool; async : bool }
22+ type t = {
33+ noclobber : bool;
44+ pipefail : bool;
55+ no_path_expansion : bool;
66+ no_unset : bool;
77+ async : bool;
88+ }
3944- let default = { noclobber = false; pipefail = false; async = false }
1010+ let default =
1111+ {
1212+ noclobber = false;
1313+ pipefail = false;
1414+ no_path_expansion = false;
1515+ no_unset = false;
1616+ async = false;
1717+ }
51866- let with_options ?noclobber ?pipefail ?async t =
1919+ let with_options ?noclobber ?pipefail ?async ?no_path_expansion ?no_unset t =
720 {
821 noclobber = Option.value ~default:t.noclobber noclobber;
922 pipefail = Option.value ~default:t.pipefail pipefail;
1023 async = Option.value ~default:t.async async;
2424+ no_path_expansion =
2525+ Option.value ~default:t.no_path_expansion no_path_expansion;
2626+ no_unset = Option.value ~default:t.no_unset no_unset;
1127 }
12281313- type posix = [ `Noclobber | `Pipefail ]
2929+ type posix = [ `Noclobber | `Pipefail | `Noglob | `Nounset ]
1430 type merry = [ `Async ]
1531 type option = [ posix | merry ]
1632···1935 (fun d -> function
2036 | `Pipefail -> with_options ~pipefail:true d
2137 | `Noclobber -> with_options ~noclobber:true d
3838+ | `Noglob -> with_options ~no_path_expansion:true d
3939+ | `Nounset -> with_options ~no_unset:true d
2240 | `Async -> with_options ~async:true d)
2341 t options
2442···2745 Fmt.pf ppf "%-12s %s@." name (if value then "on" else "off")
2846 in
2947 let opts =
3030- let { noclobber; pipefail; async } = opt in
3131- [ ("pipefail", pipefail); ("noclobber", noclobber); ("async", async) ]
4848+ let { noclobber; pipefail; async; no_path_expansion; no_unset } = opt in
4949+ [
5050+ ("pipefail", pipefail);
5151+ ("noclobber", noclobber);
5252+ ("noglob", no_path_expansion);
5353+ ("nounset", no_unset);
5454+ ("async", async);
5555+ ]
3256 in
3357 Fmt.pf ppf "@[<v>%a@]" Fmt.(list pp_option) opts
3458end
···106130 open Cmdliner
107131108132 let enum_map =
109109- [ ("pipefail", `Pipefail); ("noclobber", `Noclobber); ("async", `Async) ]
133133+ [
134134+ ("pipefail", `Pipefail);
135135+ ("noclobber", `Noclobber);
136136+ ("noglob", `Noglob);
137137+ ("nounset", `Nounset);
138138+ ("async", `Async);
139139+ ]
110140111141 let option =
112142 let doc = "Options." in
113143 Arg.(value & opt_all (enum enum_map) [] & info [ "o" ] ~docv:"OPTION" ~doc)
114144145145+ let noclobber =
146146+ let doc = "No clobber, like -o noclobber." in
147147+ Arg.(value & flag & info [ "C" ] ~docv:"NOCLOBBER" ~doc)
148148+149149+ let noglob =
150150+ let doc = "No glob, like -o noglob." in
151151+ Arg.(value & flag & info [ "f" ] ~docv:"NOGLOB" ~doc)
152152+153153+ let nounset =
154154+ let doc = "No unset, like -o nounset." in
155155+ Arg.(value & flag & info [ "u" ] ~docv:"NOUNSET" ~doc)
156156+115157 let t =
116116- let make_set update = Set { update; print_options = false } in
117117- let term = Term.(const make_set $ option) in
158158+ let make_set update noglob noclobber nounset =
159159+ let extra = if noglob then [ `Noglob ] else [] in
160160+ let extra = if noclobber then `Noclobber :: extra else extra in
161161+ let extra = if nounset then `Nounset :: extra else extra in
162162+ let update = extra @ update in
163163+ Set { update; print_options = false }
164164+ in
165165+ let term = Term.(const make_set $ option $ noglob $ noclobber $ nounset) in
118166 let info =
119167 let doc = "Set or unset options and positional parameters." in
120168 Cmd.info "set" ~doc
+19-3
src/lib/built_ins.mli
···11module Options : sig
22- type t = { noclobber : bool; pipefail : bool; async : bool }
33- type posix = [ `Noclobber | `Pipefail ]
22+ type t = {
33+ noclobber : bool;
44+ pipefail : bool;
55+ no_path_expansion : bool;
66+ no_unset : bool;
77+ async : bool;
88+ }
99+1010+ type posix = [ `Noclobber | `Pipefail | `Noglob | `Nounset ]
411 type merry = [ `Async ]
512 type option = [ posix | merry ]
613714 val default : t
88- val with_options : ?noclobber:bool -> ?pipefail:bool -> ?async:bool -> t -> t
1515+1616+ val with_options :
1717+ ?noclobber:bool ->
1818+ ?pipefail:bool ->
1919+ ?async:bool ->
2020+ ?no_path_expansion:bool ->
2121+ ?no_unset:bool ->
2222+ t ->
2323+ t
2424+925 val update : t -> option list -> t
1026 val pp : t Fmt.t
1127end
+307-241
src/lib/eval.ml
···320320 match c with
321321 | Ast.SimpleCommand (Prefixed (prefix, None, _suffix)) :: rest ->
322322 let ctx = collect_assignments ctx prefix in
323323- let job = handle_job job (`Built_in (ctx >|= fun _ -> ())) in
323323+ let job = handle_job job (`Built_in (Exit.ignore ctx)) in
324324 loop (Exit.value ctx) job stdout_of_previous rest
325325 | Ast.SimpleCommand (Prefixed (prefix, Some executable, suffix)) :: rest
326326 ->
327327 let ctx = collect_assignments ~update:false ctx prefix in
328328- let job = handle_job job (`Built_in (ctx >|= fun _ -> ())) in
328328+ let job = handle_job job (`Built_in (Exit.ignore ctx)) in
329329 loop (Exit.value ctx) job stdout_of_previous
330330 (Ast.SimpleCommand (Named (executable, suffix)) :: rest)
331331 | Ast.SimpleCommand (Named (executable, suffix)) :: rest -> (
332332 let ctx, executable = expand_cst ctx executable in
333333- let executable = handle_word_cst_subshell ctx executable in
334334- let executable, extra_args =
335335- (* This is a side-effect of the alias command with something like
333333+ match ctx with
334334+ | Exit.Nonzero _ as ctx ->
335335+ let job = handle_job job (`Built_in (Exit.ignore ctx)) in
336336+ loop (Exit.value ctx) job stdout_of_previous rest
337337+ | Exit.Zero ctx -> (
338338+ let executable = handle_word_cst_subshell ctx executable in
339339+ let executable, extra_args =
340340+ (* This is a side-effect of the alias command with something like
336341 alias ls="ls -la" *)
337337- match executable with
338338- | [ Ast.WordLiteral s ] as v -> (
339339- match String.split_on_char ' ' (remove_quotes s) with
340340- | exec :: args ->
341341- ( [ Ast.WordName exec ],
342342- List.map
343343- (fun w -> Ast.Suffix_word [ Ast.WordName w ])
344344- args )
345345- | _ -> (v, []))
346346- | v -> (v, [])
347347- in
348348- let executable = Ast.word_components_to_string executable in
349349- let ctx, suffix =
350350- match suffix with
351351- | None -> (ctx, [])
352352- | Some suffix -> expand_redirects (ctx, []) suffix
353353- in
354354- let ctx, args = args ctx (extra_args @ suffix) in
355355- let args_as_strings = List.map Ast.word_components_to_string args in
356356- let some_read, some_write =
357357- stdout_for_pipeline ~sw:pipeline_switch ctx rest
358358- in
359359- let is_global, some_write =
360360- match some_write with
361361- | `Global p -> (true, p)
362362- | `Local p -> (false, p)
363363- in
364364- let rdrs =
365365- List.fold_left
366366- (fun acc -> function
367367- | Ast.Suffix_word _ -> acc
368368- | Ast.Suffix_redirect rdr -> rdr :: acc)
369369- [] suffix
370370- |> List.rev
371371- in
372372- match handle_redirections ~sw:pipeline_switch ctx rdrs with
373373- | Error ctx -> (ctx, handle_job job (`Rdr (Exit.nonzero () 1)))
374374- | Ok rdrs -> (
375375- match Built_ins.of_args (executable :: args_as_strings) with
376376- | Some (Error _) ->
377377- (ctx, handle_job job (`Built_in (Exit.nonzero () 1)))
378378- | (None | Some (Ok (Command _))) as v -> (
379379- let is_command, command_args, print_command =
380380- match v with
381381- | Some (Ok (Command { print_command; args })) ->
382382- (true, args, print_command)
383383- | _ -> (false, [], false)
342342+ match executable with
343343+ | [ Ast.WordLiteral s ] as v -> (
344344+ match String.split_on_char ' ' (remove_quotes s) with
345345+ | exec :: args ->
346346+ ( [ Ast.WordName exec ],
347347+ List.map
348348+ (fun w -> Ast.Suffix_word [ Ast.WordName w ])
349349+ args )
350350+ | _ -> (v, []))
351351+ | v -> (v, [])
352352+ in
353353+ let executable = Ast.word_components_to_string executable in
354354+ let ctx, suffix =
355355+ match suffix with
356356+ | None -> (ctx, [])
357357+ | Some suffix -> expand_redirects (ctx, []) suffix
358358+ in
359359+ let ctx, args = args ctx (extra_args @ suffix) in
360360+ match ctx with
361361+ | Exit.Nonzero _ as ctx ->
362362+ let job = handle_job job (`Built_in (Exit.ignore ctx)) in
363363+ loop (Exit.value ctx) job stdout_of_previous rest
364364+ | Exit.Zero ctx -> (
365365+ let args_as_strings =
366366+ List.map Ast.word_components_to_string args
367367+ in
368368+ let some_read, some_write =
369369+ stdout_for_pipeline ~sw:pipeline_switch ctx rest
370370+ in
371371+ let is_global, some_write =
372372+ match some_write with
373373+ | `Global p -> (true, p)
374374+ | `Local p -> (false, p)
375375+ in
376376+ let rdrs =
377377+ List.fold_left
378378+ (fun acc -> function
379379+ | Ast.Suffix_word _ -> acc
380380+ | Ast.Suffix_redirect rdr -> rdr :: acc)
381381+ [] suffix
382382+ |> List.rev
384383 in
385385- (* We handle the [export] built_in explicitly as we need access to the
386386- raw CST *)
387387- match executable with
388388- | "export" ->
389389- let updated =
390390- handle_export_or_readonly `Export ctx args
391391- in
392392- let job =
393393- handle_job job (`Built_in (updated >|= fun _ -> ()))
394394- in
395395- loop (Exit.value updated) job stdout_of_previous rest
396396- | "readonly" ->
397397- let updated =
398398- handle_export_or_readonly `Readonly ctx args
399399- in
400400- let job =
401401- handle_job job (`Built_in (updated >|= fun _ -> ()))
402402- in
403403- loop (Exit.value updated) job stdout_of_previous rest
404404- | _ -> (
405405- let saved_ctx = ctx in
406406- let func_app =
407407- if is_command then None
408408- else
409409- let ctx = { ctx with stdout = some_write } in
410410- handle_function_application ctx ~name:executable
411411- (ctx.program :: args_as_strings)
412412- in
413413- match func_app with
414414- | Some ctx ->
415415- close_stdout ~is_global some_write;
416416- (* TODO: Proper job stuff and redirects etc. *)
417417- let job =
418418- handle_job job (`Built_in (ctx >|= fun _ -> ()))
384384+ match handle_redirections ~sw:pipeline_switch ctx rdrs with
385385+ | Error ctx -> (ctx, handle_job job (`Rdr (Exit.nonzero () 1)))
386386+ | Ok rdrs -> (
387387+ match
388388+ Built_ins.of_args (executable :: args_as_strings)
389389+ with
390390+ | Some (Error _) ->
391391+ (ctx, handle_job job (`Built_in (Exit.nonzero () 1)))
392392+ | (None | Some (Ok (Command _))) as v -> (
393393+ let is_command, command_args, print_command =
394394+ match v with
395395+ | Some (Ok (Command { print_command; args })) ->
396396+ (true, args, print_command)
397397+ | _ -> (false, [], false)
419398 in
420420- loop saved_ctx job some_read rest
421421- | None -> (
422422- match Built_ins.of_args command_args with
423423- | Some (Error _) ->
424424- ( ctx,
425425- handle_job job (`Built_in (Exit.nonzero () 1))
426426- )
427427- | Some (Ok bi) ->
428428- let ctx =
429429- handle_built_in ~rdrs ~stdout:some_write ctx bi
399399+ (* We handle the [export] built_in explicitly as we need access to the
400400+ raw CST *)
401401+ match executable with
402402+ | "export" ->
403403+ let updated =
404404+ handle_export_or_readonly `Export ctx args
405405+ in
406406+ let job =
407407+ handle_job job
408408+ (`Built_in (updated >|= fun _ -> ()))
409409+ in
410410+ loop (Exit.value updated) job stdout_of_previous
411411+ rest
412412+ | "readonly" ->
413413+ let updated =
414414+ handle_export_or_readonly `Readonly ctx args
430415 in
431431- let ctx =
432432- ctx >|= fun ctx -> clear_local_state ctx
416416+ let job =
417417+ handle_job job
418418+ (`Built_in (updated >|= fun _ -> ()))
433419 in
434434- close_stdout ~is_global some_write;
435435- let built_in = ctx >|= fun _ -> () in
436436- let job = handle_job job (`Built_in built_in) in
437437- loop (Exit.value ctx) job some_read rest
420420+ loop (Exit.value updated) job stdout_of_previous
421421+ rest
438422 | _ -> (
439439- let exec_and_args =
440440- if is_command then begin
441441- match command_args with
442442- | [] -> assert false
443443- | x :: xs -> (
444444- Eunix.with_redirections rdrs @@ fun () ->
445445- match
446446- resolve_program ~update:false ctx x
447447- with
448448- | _, None -> Exit.nonzero ("", []) 1
449449- | _, Some prog ->
450450- if print_command then
451451- Exit.zero ("echo", [ prog ])
452452- else Exit.zero (x, xs))
453453- end
454454- else Exit.zero (executable, args_as_strings)
423423+ let saved_ctx = ctx in
424424+ let func_app =
425425+ if is_command then None
426426+ else
427427+ let ctx = { ctx with stdout = some_write } in
428428+ handle_function_application ctx
429429+ ~name:executable
430430+ (ctx.program :: args_as_strings)
455431 in
456456- match exec_and_args with
457457- | Exit.Nonzero _ as v ->
432432+ match func_app with
433433+ | Some ctx ->
434434+ close_stdout ~is_global some_write;
435435+ (* TODO: Proper job stuff and redirects etc. *)
458436 let job =
459459- handle_job job
460460- (`Built_in (v >|= fun _ -> ()))
437437+ handle_job job (`Built_in (Exit.ignore ctx))
461438 in
462462- loop ctx job some_read rest
463463- | Exit.Zero (executable, args) -> (
464464- match stdout_of_previous with
465465- | None ->
466466- let ctx, job =
467467- exec_process ~sw:pipeline_switch ctx job
468468- ~fds:rdrs ~stdout:some_write
469469- ~pgid:(job_pgid job) executable args
439439+ loop saved_ctx job some_read rest
440440+ | None -> (
441441+ match Built_ins.of_args command_args with
442442+ | Some (Error _) ->
443443+ ( ctx,
444444+ handle_job job
445445+ (`Built_in (Exit.nonzero () 1)) )
446446+ | Some (Ok bi) ->
447447+ let ctx =
448448+ handle_built_in ~rdrs ~stdout:some_write
449449+ ctx bi
470450 in
471471- close_stdout ~is_global some_write;
472472- loop ctx job some_read rest
473473- | Some stdout ->
474474- let ctx, job =
475475- exec_process ~sw:pipeline_switch ctx job
476476- ~fds:rdrs ~stdin:stdout
477477- ~stdout:some_write
478478- ~pgid:(job_pgid job) executable
479479- args_as_strings
451451+ let ctx =
452452+ ctx >|= fun ctx -> clear_local_state ctx
480453 in
481454 close_stdout ~is_global some_write;
482482- loop ctx job some_read rest)))))
483483- | Some (Ok bi) ->
484484- let ctx = handle_built_in ~rdrs ~stdout:some_write ctx bi in
485485- let ctx = ctx >|= fun ctx -> clear_local_state ctx in
486486- close_stdout ~is_global some_write;
487487- let built_in = ctx >|= fun _ -> () in
488488- let job = handle_job job (`Built_in built_in) in
489489- loop (Exit.value ctx) job some_read rest))
455455+ let built_in = ctx >|= fun _ -> () in
456456+ let job =
457457+ handle_job job (`Built_in built_in)
458458+ in
459459+ loop (Exit.value ctx) job some_read rest
460460+ | _ -> (
461461+ let exec_and_args =
462462+ if is_command then begin
463463+ match command_args with
464464+ | [] -> assert false
465465+ | x :: xs -> (
466466+ Eunix.with_redirections rdrs
467467+ @@ fun () ->
468468+ match
469469+ resolve_program ~update:false
470470+ ctx x
471471+ with
472472+ | _, None ->
473473+ Exit.nonzero ("", []) 1
474474+ | _, Some prog ->
475475+ if print_command then
476476+ Exit.zero ("echo", [ prog ])
477477+ else Exit.zero (x, xs))
478478+ end
479479+ else
480480+ Exit.zero (executable, args_as_strings)
481481+ in
482482+ match exec_and_args with
483483+ | Exit.Nonzero _ as v ->
484484+ let job =
485485+ handle_job job
486486+ (`Built_in (Exit.ignore v))
487487+ in
488488+ loop ctx job some_read rest
489489+ | Exit.Zero (executable, args) -> (
490490+ match stdout_of_previous with
491491+ | None ->
492492+ let ctx, job =
493493+ exec_process ~sw:pipeline_switch
494494+ ctx job ~fds:rdrs
495495+ ~stdout:some_write
496496+ ~pgid:(job_pgid job)
497497+ executable args
498498+ in
499499+ close_stdout ~is_global some_write;
500500+ loop ctx job some_read rest
501501+ | Some stdout ->
502502+ let ctx, job =
503503+ exec_process ~sw:pipeline_switch
504504+ ctx job ~fds:rdrs
505505+ ~stdin:stdout
506506+ ~stdout:some_write
507507+ ~pgid:(job_pgid job)
508508+ executable args_as_strings
509509+ in
510510+ close_stdout ~is_global some_write;
511511+ loop ctx job some_read rest)))))
512512+ | Some (Ok bi) ->
513513+ let ctx =
514514+ handle_built_in ~rdrs ~stdout:some_write ctx bi
515515+ in
516516+ let ctx = ctx >|= fun ctx -> clear_local_state ctx in
517517+ close_stdout ~is_global some_write;
518518+ let built_in = ctx >|= fun _ -> () in
519519+ let job = handle_job job (`Built_in built_in) in
520520+ loop (Exit.value ctx) job some_read rest))))
490521 | CompoundCommand (c, rdrs) :: rest -> (
491522 match handle_redirections ~sw:pipeline_switch ctx rdrs with
492523 | Error ctx -> (ctx, handle_job job (`Rdr (Exit.nonzero () 1)))
···569600 expand ([ Ast.WordName "" ] :: acc) ctx rest)
570601 | Ast.VariableAtom (s, NoAttribute) -> (
571602 match S.lookup ctx.state ~param:s with
572572- | None -> expand ([ Ast.WordName "" ] :: acc) ctx rest
603603+ | None ->
604604+ if ctx.options.no_unset then begin
605605+ ( Exit.nonzero_msg ctx ~exit_code:1 "%s: unbound variable" s,
606606+ List.rev acc |> List.concat )
607607+ end
608608+ else expand ([ Ast.WordName "" ] :: acc) ctx rest
573609 | Some cst -> expand (cst :: acc) ctx rest)
574610 | Ast.VariableAtom (s, ParameterLength) -> (
575611 match S.lookup ctx.state ~param:s with
···592628 (( RemoveSmallestPrefixPattern cst
593629 | RemoveLargestPrefixPattern cst ) as v) ) -> (
594630 let ctx, spp = expand_cst ctx cst in
595595- let pattern = Ast.word_components_to_string spp in
596596- match S.lookup ctx.state ~param:s with
597597- | None -> expand (cst :: acc) ctx rest
598598- | Some cst -> (
599599- let kind =
600600- match v with
601601- | RemoveSmallestPrefixPattern _ -> `Smallest
602602- | RemoveLargestPrefixPattern _ -> `Largest
603603- | _ -> assert false
604604- in
605605- let param = Ast.word_components_to_string cst in
606606- let prefix = get_prefix ~pattern ~kind param in
607607- match prefix with
608608- | None -> expand ([ Ast.WordName param ] :: acc) ctx rest
609609- | Some s -> (
610610- match String.cut_prefix ~prefix:s param with
611611- | Some s -> expand ([ Ast.WordName s ] :: acc) ctx rest
612612- | None -> expand ([ Ast.WordName param ] :: acc) ctx rest)
613613- ))
631631+ match ctx with
632632+ | Exit.Nonzero _ as ctx -> (ctx, List.rev acc |> List.concat)
633633+ | Exit.Zero ctx -> (
634634+ let pattern = Ast.word_components_to_string spp in
635635+ match S.lookup ctx.state ~param:s with
636636+ | None -> expand (cst :: acc) ctx rest
637637+ | Some cst -> (
638638+ let kind =
639639+ match v with
640640+ | RemoveSmallestPrefixPattern _ -> `Smallest
641641+ | RemoveLargestPrefixPattern _ -> `Largest
642642+ | _ -> assert false
643643+ in
644644+ let param = Ast.word_components_to_string cst in
645645+ let prefix = get_prefix ~pattern ~kind param in
646646+ match prefix with
647647+ | None -> expand ([ Ast.WordName param ] :: acc) ctx rest
648648+ | Some s -> (
649649+ match String.cut_prefix ~prefix:s param with
650650+ | Some s ->
651651+ expand ([ Ast.WordName s ] :: acc) ctx rest
652652+ | None ->
653653+ expand ([ Ast.WordName param ] :: acc) ctx rest)))
654654+ )
614655 | Ast.VariableAtom
615656 ( s,
616657 (( RemoveSmallestSuffixPattern cst
617658 | RemoveLargestSuffixPattern cst ) as v) ) -> (
618659 let ctx, spp = expand_cst ctx cst in
619660 let pattern = Ast.word_components_to_string spp in
620620- match S.lookup ctx.state ~param:s with
621621- | None -> expand (cst :: acc) ctx rest
622622- | Some cst -> (
623623- let kind =
624624- match v with
625625- | RemoveSmallestSuffixPattern _ -> `Smallest
626626- | RemoveLargestSuffixPattern _ -> `Largest
627627- | _ -> assert false
628628- in
629629- let param = Ast.word_components_to_string cst in
630630- let suffix = get_suffix ~pattern ~kind param in
631631- match suffix with
632632- | None -> expand ([ Ast.WordName param ] :: acc) ctx rest
633633- | Some s -> (
634634- match String.cut_suffix ~suffix:s param with
635635- | Some s -> expand ([ Ast.WordName s ] :: acc) ctx rest
636636- | None -> expand ([ Ast.WordName param ] :: acc) ctx rest)
637637- ))
661661+ match ctx with
662662+ | Exit.Nonzero _ as ctx -> (ctx, List.rev acc |> List.concat)
663663+ | Exit.Zero ctx -> (
664664+ match S.lookup ctx.state ~param:s with
665665+ | None -> expand (cst :: acc) ctx rest
666666+ | Some cst -> (
667667+ let kind =
668668+ match v with
669669+ | RemoveSmallestSuffixPattern _ -> `Smallest
670670+ | RemoveLargestSuffixPattern _ -> `Largest
671671+ | _ -> assert false
672672+ in
673673+ let param = Ast.word_components_to_string cst in
674674+ let suffix = get_suffix ~pattern ~kind param in
675675+ match suffix with
676676+ | None -> expand ([ Ast.WordName param ] :: acc) ctx rest
677677+ | Some s -> (
678678+ match String.cut_suffix ~suffix:s param with
679679+ | Some s ->
680680+ expand ([ Ast.WordName s ] :: acc) ctx rest
681681+ | None ->
682682+ expand ([ Ast.WordName param ] :: acc) ctx rest)))
683683+ )
638684 | Ast.VariableAtom (s, UseAlternativeValue (_, alt)) -> (
639685 match S.lookup ctx.state ~param:s with
640686 | Some _ -> expand (alt :: acc) ctx rest
···718764 end;
719765 Exit.zero ctx
720766721721- and expand_cst (ctx : ctx) cst : ctx * Ast.word_cst =
767767+ and expand_cst (ctx : ctx) cst : ctx Exit.t * Ast.word_cst =
722768 let cst = tilde_expansion ctx cst in
723769 let ctx, cst = parameter_expansion' ctx cst in
724770 match ctx with
725725- | Exit.Nonzero { value = ctx; _ } -> (ctx, cst)
771771+ | Exit.Nonzero _ as ctx -> (ctx, cst)
726772 | Exit.Zero ctx ->
727773 (* TODO: Propagate errors *)
728774 let ctx, ast = arithmetic_expansion ctx cst in
729729- (ctx, ast)
775775+ (Exit.zero ctx, ast)
730776731777 and expand_redirects ((ctx, acc) : ctx * Ast.cmd_suffix_item list)
732778 (c : Ast.cmd_suffix_item list) =
733779 match c with
734780 | [] -> (ctx, List.rev acc)
735735- | Ast.Suffix_redirect (IoRedirect_IoFile (num, (op, file))) :: rest ->
781781+ | Ast.Suffix_redirect (IoRedirect_IoFile (num, (op, file))) :: rest -> (
736782 let ctx, cst = expand_cst ctx file in
737737- let cst = handle_subshell ctx cst in
738738- let v = Ast.Suffix_redirect (IoRedirect_IoFile (num, (op, cst))) in
739739- expand_redirects (ctx, v :: acc) rest
783783+ match ctx with
784784+ | Exit.Nonzero _ -> assert false
785785+ | Exit.Zero ctx ->
786786+ let cst = handle_subshell ctx cst in
787787+ let v = Ast.Suffix_redirect (IoRedirect_IoFile (num, (op, cst))) in
788788+ expand_redirects (ctx, v :: acc) rest)
740789 | (Ast.Suffix_redirect _ as v) :: rest ->
741790 expand_redirects (ctx, v :: acc) rest
742791 | s :: rest -> expand_redirects (ctx, s :: acc) rest
···824873 | Ast.Case _ -> Exit.zero ctx
825874 | Cases (word, case_list) -> (
826875 let ctx, word = expand_cst ctx word in
827827- let scrutinee = Ast.word_components_to_string word in
828828- let res =
829829- Nlist.fold_left
830830- (fun acc pat ->
831831- match acc with
832832- | Some _ as ctx -> ctx
833833- | None -> (
834834- match pat with
835835- | Ast.Case_pattern (p, sub) ->
836836- Nlist.fold_left
837837- (fun inner_acc pattern ->
838838- match inner_acc with
839839- | Some _ as v -> v
840840- | None ->
841841- let ctx, pattern = expand_cst ctx pattern in
842842- let pattern =
843843- Ast.word_components_to_string pattern
844844- in
845845- if Glob.test ~pattern scrutinee then begin
846846- match sub with
847847- | Some sub -> Some (exec_subshell ctx sub)
848848- | None -> Some (Exit.zero ctx)
849849- end
850850- else inner_acc)
851851- None p))
852852- None case_list
853853- in
854854- match res with Some ctx -> ctx | None -> Exit.zero ctx)
876876+ match ctx with
877877+ | Exit.Nonzero _ as ctx -> ctx
878878+ | Exit.Zero ctx -> (
879879+ let scrutinee = Ast.word_components_to_string word in
880880+ let res =
881881+ Nlist.fold_left
882882+ (fun acc pat ->
883883+ match acc with
884884+ | Some _ as ctx -> ctx
885885+ | None -> (
886886+ match pat with
887887+ | Ast.Case_pattern (p, sub) ->
888888+ Nlist.fold_left
889889+ (fun inner_acc pattern ->
890890+ match inner_acc with
891891+ | Some _ as v -> v
892892+ | None -> (
893893+ let ctx, pattern = expand_cst ctx pattern in
894894+ match ctx with
895895+ | Exit.Nonzero _ as ctx -> Some ctx
896896+ | Exit.Zero ctx ->
897897+ let pattern =
898898+ Ast.word_components_to_string pattern
899899+ in
900900+ if Glob.test ~pattern scrutinee then begin
901901+ match sub with
902902+ | Some sub ->
903903+ Some (exec_subshell ctx sub)
904904+ | None -> Some (Exit.zero ctx)
905905+ end
906906+ else inner_acc))
907907+ None p))
908908+ None case_list
909909+ in
910910+ match res with Some ctx -> ctx | None -> Exit.zero ctx))
855911856912 and exec_subshell ctx (term, sep) =
857913 let saved_ctx = ctx in
···94510019461002 and glob_expand ctx wc =
9471003 let wc = handle_word_cst_subshell ctx wc in
948948- if Ast.has_glob wc then
10041004+ if Ast.has_glob wc && not ctx.options.no_path_expansion then
9491005 Ast.word_components_to_string wc |> fun pattern ->
9501006 Glob.glob_dir ~pattern (cwd_of_ctx ctx)
9511007 |> List.map (fun w -> [ Ast.WordName w ])
···9651021 | Ast.Prefix_assignment (Name param, v) -> (
9661022 (* Expand the values *)
9671023 let ctx, v = expand_cst ctx v in
968968- let v = handle_subshell ctx v in
969969- let state =
970970- if update then S.update ctx.state ~param v else Ok ctx.state
971971- in
972972- match state with
973973- | Error message -> Exit.nonzero ~message ctx 1
974974- | Ok state ->
975975- Exit.zero
976976- {
977977- ctx with
978978- state;
979979- local_state =
980980- (param, Ast.word_components_to_string v)
981981- :: ctx.local_state;
982982- })
10241024+ match ctx with
10251025+ | Exit.Nonzero _ as ctx -> ctx
10261026+ | Exit.Zero ctx -> (
10271027+ let v = handle_subshell ctx v in
10281028+ let state =
10291029+ if update then S.update ctx.state ~param v
10301030+ else Ok ctx.state
10311031+ in
10321032+ match state with
10331033+ | Error message -> Exit.nonzero ~message ctx 1
10341034+ | Ok state ->
10351035+ Exit.zero
10361036+ {
10371037+ ctx with
10381038+ state;
10391039+ local_state =
10401040+ (param, Ast.word_components_to_string v)
10411041+ :: ctx.local_state;
10421042+ }))
9831043 | _ -> Exit.zero ctx))
9841044 (Exit.zero ctx) vs
9851045986986- and args ctx swc : ctx * Ast.word_cst list =
10461046+ and args ctx swc : ctx Exit.t * Ast.word_cst list =
9871047 List.fold_left
9881048 (fun (ctx, acc) -> function
9891049 | Ast.Suffix_redirect _ -> (ctx, acc)
990990- | Suffix_word wc ->
991991- let ctx, cst = expand_cst ctx wc in
992992- (ctx, acc @ word_glob_expand ctx cst))
993993- (ctx, []) swc
10501050+ | Suffix_word wc -> (
10511051+ match ctx with
10521052+ | Exit.Nonzero _ as ctx -> (ctx, acc)
10531053+ | Exit.Zero ctx -> (
10541054+ let ctx, cst = expand_cst ctx wc in
10551055+ match ctx with
10561056+ | Exit.Nonzero _ as ctx -> (ctx, acc)
10571057+ | Exit.Zero c as ctx -> (ctx, acc @ word_glob_expand c cst))))
10581058+ (Exit.zero ctx, [])
10591059+ swc
99410609951061 and handle_built_in ~rdrs ~(stdout : Eio_unix.sink_ty Eio.Flow.sink)
9961062 (ctx : ctx) v =
+4
src/lib/exit.ml
···1717 should_exit : should_exit;
1818 }
19192020+let ignore = function
2121+ | Zero _ -> Zero ()
2222+ | Nonzero v -> Nonzero { v with value = () }
2323+2024let value = function Zero v -> v | Nonzero { value; _ } -> value
21252226let not = function
+33
test/options.t
···11+More fine-grained testing of the possible shell execution options.
22+33+First, we test the noglob option.
44+55+ $ cat > test.sh << EOF
66+ > set -f
77+ > touch a.txt b.txt
88+ > ls *.txt
99+ > EOF
1010+1111+ $ sh test.sh
1212+ ls: cannot access '*.txt': No such file or directory
1313+ [2]
1414+ $ msh test.sh
1515+ ls: cannot access '*.txt': No such file or directory
1616+ [2]
1717+1818+No unset variable option.
1919+2020+ $ cat > test.sh << EOF
2121+ > echo "The variable is: \$UNSETVAR"
2222+ > set -u
2323+ > echo \$UNSETVAR
2424+ > EOF
2525+2626+ $ sh test.sh
2727+ The variable is:
2828+ test.sh: line 3: UNSETVAR: unbound variable
2929+ [1]
3030+ $ msh test.sh
3131+ The variable is:
3232+ UNSETVAR: unbound variable
3333+ [1]