···2929 program = pos_zero;
3030 functions = [];
3131 hash = Merry.Hash.empty;
3232+ rdrs = [];
3233 }
3334 in
3435 match (file, command) with
+3-2
src/lib/ast.ml
···9494 | Command_CompoundCommand a ->
9595 let a = compound_command a.value in
9696 CompoundCommand (a, [])
9797- | Command_CompoundCommand_RedirectList (a, _) ->
9797+ | Command_CompoundCommand_RedirectList (a, rdrs) ->
9898 let a = compound_command a.value in
9999- CompoundCommand (a, [])
9999+ let b = redirect_list rdrs.value in
100100+ CompoundCommand (a, b)
100101 | Command_FunctionDefinition a ->
101102 let a = function_definition a.value in
102103 FunctionDefinition a
+132-113
src/lib/eval.ml
···4646 argv : string array;
4747 functions : (string * Ast.compound_command) list;
4848 hash : Hash.t;
4949+ rdrs : Types.redirect list;
4950 }
50515152 let clear_local_state ctx = { ctx with local_state = [] }
···151152 | (Io_op_great | Io_op_dgreat) as v ->
152153 (* Simple file creation *)
153154 let append = v = Io_op_dgreat in
155155+ let create =
156156+ if append then `Never
157157+ else if ctx.options.noclobber then `Exclusive 0o644
158158+ else `Or_truncate 0o644
159159+ in
154160 let w =
155155- Eio.Path.open_out ~sw ~append ~create:(`If_missing 0o644)
161161+ Eio.Path.open_out ~sw ~append ~create
156162 (ctx.fs / Ast.word_components_to_string file)
157163 in
158164 let fd = Eio_unix.Resource.fd_opt w |> Option.get in
···183189 Types.Redirect (1, fd, `Blocking);
184190 Types.Redirect (2, fd, `Blocking);
185191 ]
186186- | Io_op_clobber -> Fmt.failwith ">| not supported yet."
192192+ | Io_op_clobber ->
193193+ let w =
194194+ Eio.Path.open_out ~sw ~create:(`Or_truncate 0o644)
195195+ (ctx.fs / Ast.word_components_to_string file)
196196+ in
197197+ let fd = Eio_unix.Resource.fd_opt w |> Option.get in
198198+ [ Types.Redirect (n, fd, `Blocking) ]
187199 | Io_op_lessgreat -> Fmt.failwith "<> not support yet.")
188200 | Ast.IoRedirect_IoHere _ ->
189201 Fmt.failwith "HERE documents not yet implemented!"
190202203203+ let handle_redirections ~sw ctx rdrs =
204204+ try Ok (List.concat_map (handle_one_redirection ~sw ctx) rdrs)
205205+ with Eio.Io (Eio.Fs.E (Already_exists _), _) ->
206206+ Fmt.epr "msh: cannot overwrite existing file\n%!";
207207+ Error ctx
208208+191209 let cwd_of_ctx ctx = S.cwd ctx.state |> Fpath.to_string |> ( / ) ctx.fs
192210193211 let needs_glob_expansion : Ast.word_component -> bool = function
···199217200218 let resolve_program ?(update = true) ctx name =
201219 let v =
202202- (* Fmt.epr "Resolving %s\n%!" name; *)
203220 if not (String.contains name '/') then begin
204204- (* Fmt.epr "not %a\n%!" Fmt.(option string) (S.lookup ctx.state ~param:"PATH" |> Option.map Ast.word_components_to_string); *)
205221 S.lookup ctx.state ~param:"PATH"
206222 |> Option.map Ast.word_components_to_string
207223 |> Option.value ~default:"/bin:/usr/bin"
208224 |> String.split_on_char ':'
209225 |> List.find_map (fun dir ->
210226 let p = Filename.concat dir name in
211211- (* Fmt.epr "Does it exist %s %b\n%!" p (Sys.file_exists p); *)
212227 if Sys.file_exists p then Some p else None)
213228 end
214229 else if Sys.file_exists name then Some name
···247262 | None -> ctx
248263 | Some process -> set_last_background ~async process ctx
249264 in
250250- let handle_job j p =
251251- match p with
252252- (* | None, _ -> *)
253253- (* let pgid = match pgid with Some p -> p | None -> Unix.getpid () in *)
254254- (* Option.some *)
255255- (* @@ J.make ~state:`Running ~reap:(Option.get reap) pgid *)
256256- (* (Nlist.Singleton p) *)
265265+ let handle_job j = function
257266 | `Process p -> J.add_process p j
267267+ | `Rdr p -> J.add_rdr p j
258268 | `Built_in p -> J.add_built_in p j
259269 | `Error p -> J.add_error p j
260270 in
···267277 let pgid = match pgid with None -> 0 | Some p -> p in
268278 let reap = J.get_reaper job in
269279 let mode = if async then Types.Async else Types.Switched sw in
280280+ let fds = ctx.rdrs @ Option.value ~default:[] fds in
270281 let ctx, process =
271282 match (executable, resolve_program ctx executable) with
272283 | _, (ctx, None) | "", (ctx, _) ->
273273- Eunix.with_redirections
274274- (match fds with None -> [] | Some ls -> ls)
275275- (fun () ->
284284+ Eunix.with_redirections fds (fun () ->
276285 Eio.Flow.copy_string
277286 (Fmt.str "msh: command not found: %s\n" executable)
278287 stdout);
279288 (ctx, Error (127, `Not_found))
280289 | _, (ctx, Some full_path) ->
281290 ( ctx,
282282- E.exec ctx.executor ~delay_reap:(fst reap) ?fds ?stdin ~stdout
291291+ E.exec ctx.executor ~delay_reap:(fst reap) ~fds ?stdin ~stdout
283292 ~pgid ~mode ~cwd:(cwd_of_ctx ctx)
284293 ~env:(get_env ~extra:ctx.local_state ctx)
285294 ~executable:full_path (executable :: args) )
···347356 List.fold_left
348357 (fun acc -> function
349358 | Ast.Suffix_word _ -> acc
350350- | Ast.Suffix_redirect rdr ->
351351- handle_one_redirection ~sw:pipeline_switch ctx rdr @ acc)
359359+ | Ast.Suffix_redirect rdr -> rdr :: acc)
352360 [] suffix
353361 |> List.rev
354362 in
355355- match Built_ins.of_args (executable :: args_as_strings) with
356356- | Some (Error _) ->
357357- (ctx, handle_job job (`Built_in (Exit.nonzero () 1)))
358358- | (None | Some (Ok (Command _))) as v -> (
359359- let is_command, command_args, print_command =
360360- match v with
361361- | Some (Ok (Command { print_command; args })) ->
362362- (true, args, print_command)
363363- | _ -> (false, [], false)
364364- in
365365- (* We handle the [export] built_in explicitly as we need access to the
363363+ match handle_redirections ~sw:pipeline_switch ctx rdrs with
364364+ | Error ctx -> (ctx, handle_job job (`Rdr (Exit.nonzero () 1)))
365365+ | Ok rdrs -> (
366366+ match Built_ins.of_args (executable :: args_as_strings) with
367367+ | Some (Error _) ->
368368+ (ctx, handle_job job (`Built_in (Exit.nonzero () 1)))
369369+ | (None | Some (Ok (Command _))) as v -> (
370370+ let is_command, command_args, print_command =
371371+ match v with
372372+ | Some (Ok (Command { print_command; args })) ->
373373+ (true, args, print_command)
374374+ | _ -> (false, [], false)
375375+ in
376376+ (* We handle the [export] built_in explicitly as we need access to the
366377 raw CST *)
367367- match executable with
368368- | "export" ->
369369- let updated = handle_export ctx args in
370370- let job =
371371- handle_job job (`Built_in (updated >|= fun _ -> ()))
372372- in
373373- loop (Exit.value updated) job stdout_of_previous rest
374374- | _ -> (
375375- let saved_ctx = ctx in
376376- let func_app =
377377- if is_command then None
378378- else
379379- let ctx = { ctx with stdout = some_write } in
380380- handle_function_application ctx ~name:executable
381381- (ctx.program :: args_as_strings)
382382- in
383383- match func_app with
384384- | Some ctx ->
385385- close_stdout ~is_global some_write;
386386- (* TODO: Proper job stuff and redirects etc. *)
378378+ match executable with
379379+ | "export" ->
380380+ let updated = handle_export ctx args in
387381 let job =
388388- handle_job job (`Built_in (ctx >|= fun _ -> ()))
382382+ handle_job job (`Built_in (updated >|= fun _ -> ()))
389383 in
390390- loop saved_ctx job some_read rest
391391- | None -> (
392392- match Built_ins.of_args command_args with
393393- | Some (Error _) ->
394394- (ctx, handle_job job (`Built_in (Exit.nonzero () 1)))
395395- | Some (Ok bi) ->
396396- let ctx =
397397- handle_built_in ~rdrs ~stdout:some_write ctx bi
398398- in
384384+ loop (Exit.value updated) job stdout_of_previous rest
385385+ | _ -> (
386386+ let saved_ctx = ctx in
387387+ let func_app =
388388+ if is_command then None
389389+ else
390390+ let ctx = { ctx with stdout = some_write } in
391391+ handle_function_application ctx ~name:executable
392392+ (ctx.program :: args_as_strings)
393393+ in
394394+ match func_app with
395395+ | Some ctx ->
399396 close_stdout ~is_global some_write;
400400- let built_in = ctx >|= fun _ -> () in
401401- let job = handle_job job (`Built_in built_in) in
402402- loop (Exit.value ctx) job some_read rest
403403- | _ -> (
404404- let exec_and_args =
405405- if is_command then begin
406406- match command_args with
407407- | [] -> assert false
408408- | x :: xs -> (
409409- Eunix.with_redirections rdrs @@ fun () ->
410410- match resolve_program ~update:false ctx x with
411411- | _, None -> Exit.nonzero ("", []) 1
412412- | _, Some prog ->
413413- if print_command then
414414- Exit.zero ("echo", [ prog ])
415415- else Exit.zero (x, xs))
416416- end
417417- else Exit.zero (executable, args_as_strings)
397397+ (* TODO: Proper job stuff and redirects etc. *)
398398+ let job =
399399+ handle_job job (`Built_in (ctx >|= fun _ -> ()))
418400 in
419419- match exec_and_args with
420420- | Exit.Nonzero _ as v ->
421421- let job =
422422- handle_job job (`Built_in (v >|= fun _ -> ()))
401401+ loop saved_ctx job some_read rest
402402+ | None -> (
403403+ match Built_ins.of_args command_args with
404404+ | Some (Error _) ->
405405+ ( ctx,
406406+ handle_job job (`Built_in (Exit.nonzero () 1))
407407+ )
408408+ | Some (Ok bi) ->
409409+ let ctx =
410410+ handle_built_in ~rdrs ~stdout:some_write ctx bi
411411+ in
412412+ close_stdout ~is_global some_write;
413413+ let built_in = ctx >|= fun _ -> () in
414414+ let job = handle_job job (`Built_in built_in) in
415415+ loop (Exit.value ctx) job some_read rest
416416+ | _ -> (
417417+ let exec_and_args =
418418+ if is_command then begin
419419+ match command_args with
420420+ | [] -> assert false
421421+ | x :: xs -> (
422422+ Eunix.with_redirections rdrs @@ fun () ->
423423+ match
424424+ resolve_program ~update:false ctx x
425425+ with
426426+ | _, None -> Exit.nonzero ("", []) 1
427427+ | _, Some prog ->
428428+ if print_command then
429429+ Exit.zero ("echo", [ prog ])
430430+ else Exit.zero (x, xs))
431431+ end
432432+ else Exit.zero (executable, args_as_strings)
423433 in
424424- loop ctx job some_read rest
425425- | Exit.Zero (executable, args) -> (
426426- match stdout_of_previous with
427427- | None ->
428428- let ctx, job =
429429- exec_process ~sw:pipeline_switch ctx job
430430- ~fds:rdrs ~stdout:some_write
431431- ~pgid:(job_pgid job) executable args
434434+ match exec_and_args with
435435+ | Exit.Nonzero _ as v ->
436436+ let job =
437437+ handle_job job
438438+ (`Built_in (v >|= fun _ -> ()))
432439 in
433433- close_stdout ~is_global some_write;
434440 loop ctx job some_read rest
435435- | Some stdout ->
436436- let ctx, job =
437437- exec_process ~sw:pipeline_switch ctx job
438438- ~fds:rdrs ~stdin:stdout ~stdout:some_write
439439- ~pgid:(job_pgid job) executable
440440- args_as_strings
441441- in
442442- close_stdout ~is_global some_write;
443443- loop ctx job some_read rest)))))
444444- | Some (Ok bi) ->
445445- let ctx = handle_built_in ~rdrs ~stdout:some_write ctx bi in
446446- close_stdout ~is_global some_write;
447447- let built_in = ctx >|= fun _ -> () in
448448- let job = handle_job job (`Built_in built_in) in
449449- loop (Exit.value ctx) job some_read rest)
450450- | CompoundCommand (c, rdrs) :: rest ->
451451- let _rdrs =
452452- List.map (handle_one_redirection ~sw:pipeline_switch ctx) rdrs
453453- in
454454- (* TODO: No way this is right *)
455455- let ctx = handle_compound_command ctx c in
456456- let job = handle_job job (`Built_in (ctx >|= fun _ -> ())) in
457457- loop (Exit.value ctx) job None rest
441441+ | Exit.Zero (executable, args) -> (
442442+ match stdout_of_previous with
443443+ | None ->
444444+ let ctx, job =
445445+ exec_process ~sw:pipeline_switch ctx job
446446+ ~fds:rdrs ~stdout:some_write
447447+ ~pgid:(job_pgid job) executable args
448448+ in
449449+ close_stdout ~is_global some_write;
450450+ loop ctx job some_read rest
451451+ | Some stdout ->
452452+ let ctx, job =
453453+ exec_process ~sw:pipeline_switch ctx job
454454+ ~fds:rdrs ~stdin:stdout
455455+ ~stdout:some_write
456456+ ~pgid:(job_pgid job) executable
457457+ args_as_strings
458458+ in
459459+ close_stdout ~is_global some_write;
460460+ loop ctx job some_read rest)))))
461461+ | Some (Ok bi) ->
462462+ let ctx = handle_built_in ~rdrs ~stdout:some_write ctx bi in
463463+ close_stdout ~is_global some_write;
464464+ let built_in = ctx >|= fun _ -> () in
465465+ let job = handle_job job (`Built_in built_in) in
466466+ loop (Exit.value ctx) job some_read rest))
467467+ | CompoundCommand (c, rdrs) :: rest -> (
468468+ match handle_redirections ~sw:pipeline_switch ctx rdrs with
469469+ | Error ctx -> (ctx, handle_job job (`Rdr (Exit.nonzero () 1)))
470470+ | Ok rdrs ->
471471+ (* TODO: No way this is right *)
472472+ let ctx = { ctx with rdrs } in
473473+ let ctx = handle_compound_command ctx c in
474474+ let job = handle_job job (`Built_in (ctx >|= fun _ -> ())) in
475475+ let actual_ctx = Exit.value ctx in
476476+ loop { actual_ctx with rdrs = [] } job None rest)
458477 | FunctionDefinition (name, (body, _rdrs)) :: rest ->
459478 let ctx = { ctx with functions = (name, body) :: ctx.functions } in
460479 loop ctx job None rest
+7-2
src/lib/job.ml
···55 id : int;
66 (* Process list is in reverse order *)
77 processes :
88- [ `Process of E.process | `Built_in of unit Exit.t | `Error of int ] list;
88+ [ `Process of E.process
99+ | `Built_in of unit Exit.t
1010+ | `Rdr of unit Exit.t
1111+ | `Error of int ]
1212+ list;
913 }
10141115 let get_reaper t = t.reap
···2125 { t with processes = List.cons (`Built_in b) t.processes }
22262327 let add_error b t = { t with processes = List.cons (`Error b) t.processes }
2828+ let add_rdr b t = { t with processes = List.cons (`Rdr b) t.processes }
24292530 (* Section 2.9.2 https://pubs.opengroup.org/onlinepubs/9799919799/ *)
2631 let await_exit ~pipefail ~interactive t =
···3136 if interactive then
3237 Eunix.delegate_control ~pgid:t.id @@ fun () -> E.await p
3338 else E.await p
3434- | `Built_in b -> b
3939+ | `Built_in b | `Rdr b -> b
3540 | `Error n -> Exit.nonzero () n
3641 in
3742 match (pipefail, t.processes) with
+19
test/forloops.t
···6161 olleh
6262 dlrow
63636464+1.6 Redirects
6565+6666+ $ cat > test.sh << EOF
6767+ > for i in a b; do
6868+ > echo \$i
6969+ > done > hello.txt
7070+ > echo "The file contains:"
7171+ > cat hello.txt
7272+ > EOF
7373+7474+ $ sh test.sh
7575+ The file contains:
7676+ a
7777+ b
7878+ $ msh test.sh
7979+ The file contains:
8080+ a
8181+ b
8282+