···9494 | Return of int
9595 | Umask of int option
9696 | Shift of int option
9797+ | Read of bool * string list
97989899let reserved = [ "fg"; "bg"; "jobs" ]
99100let pp_args = Fmt.(list ~sep:(Fmt.any " ") string)
···126127 | Ignore -> "ignore"
127128 | Default -> "default")
128129 | Set _ -> "set"
130130+ | Read (backslash, vars) ->
131131+ Fmt.str "read%s %a" (if backslash then " -r" else " ") pp_args vars
129132130133(* Change Directory *)
131134module Cd = struct
···495498 Cmd.v info term
496499end
497500501501+module Read = struct
502502+ open Cmdliner
503503+504504+ let r =
505505+ let doc =
506506+ "Do not treat <backslash> character in any special way. Consider each \
507507+ <backslash> to be part of the input line."
508508+ in
509509+ Arg.(value & flag & info [ "r" ] ~doc)
510510+511511+ let args =
512512+ let doc = "Names of existing or non-existing shell variables." in
513513+ Arg.(value & pos_all string [] & info [] ~docv:"var" ~doc)
514514+515515+ let t =
516516+ let make_read r args = Read (r, args) in
517517+ let term = Term.(const make_read $ r $ args) in
518518+ let info =
519519+ let doc = "Read from standard input into shell variables." in
520520+ Cmd.info "read" ~doc
521521+ in
522522+ Cmd.v info term
523523+end
524524+498525let of_args (w : string list) =
499526 let open Cmdliner in
500527 let exec_cmd cmd v =
···524551 | "return" :: _ as cmd -> exec_cmd cmd Return.t
525552 | "umask" :: _ as cmd -> exec_cmd cmd Umask.t
526553 | "shift" :: _ as cmd -> exec_cmd cmd Shift.t
554554+ | "read" :: _ as cmd -> exec_cmd cmd Read.t
527555 | cmd :: _ ->
528556 if List.mem cmd reserved then begin
529557 Debug.Log.err (fun f -> f "Unimplemented built-in: %s" cmd);
+1
src/lib/built_ins.mli
···5353 | Return of int
5454 | Umask of int option
5555 | Shift of int option
5656+ | Read of bool * string list
56575758val to_string : t -> string
5859(** Serialises a built-in to a string *)
+36
src/lib/eval.ml
···13651365 assert (new_len >= 0);
13661366 let argv = Array.init new_len (fun i -> Array.get ctx.argv (i + n)) in
13671367 Exit.zero { ctx with argv }
13681368+ | Read (_backslash, vars) -> (
13691369+ let line =
13701370+ let buf = Cstruct.create 1 in
13711371+ let rec loop acc =
13721372+ match
13731373+ Eio.Flow.read_exact ctx.stdin buf;
13741374+ Cstruct.to_string buf
13751375+ with
13761376+ | "\n" -> Some acc
13771377+ | c -> loop (acc ^ c)
13781378+ | exception End_of_file -> None
13791379+ in
13801380+ loop ""
13811381+ in
13821382+ let rec loop acc = function
13831383+ | v :: vars, Ast.{ txt; _ } :: fs -> loop ((v, txt) :: acc) (vars, fs)
13841384+ | _, [] -> List.rev acc
13851385+ | [], lines ->
13861386+ let last_var, last_line = List.hd acc in
13871387+ List.rev
13881388+ ((last_var, last_line ^ Ast.Fragment.join_list ~sep:"" lines)
13891389+ :: acc)
13901390+ in
13911391+ let fields =
13921392+ Option.map (fun s -> field_splitting ctx [ Ast.Fragment.make s ]) line
13931393+ in
13941394+ match fields with
13951395+ | None -> Exit.nonzero ctx 1
13961396+ | Some fs ->
13971397+ let vars = loop [] (vars, fs) in
13981398+ let state =
13991399+ List.fold_left
14001400+ (fun st (k, v) -> S.update st ~param:k v |> Result.get_ok)
14011401+ ctx.state vars
14021402+ in
14031403+ Exit.zero { ctx with state })
13681404 | Command _ ->
13691405 (* Handled separately *)
13701406 assert false
+35
test/built_ins.t
···328328 args: a b c d
329329 args 2: c d
330330 args 2: 2
331331+332332+15. Read
333333+334334+335335+ $ cat > test.sh << EOF
336336+ > echo "hello world" | read FOO BAR
337337+ > echo "FOO is \$FOO"
338338+ > echo "BAR is \$BAR"
339339+ > EOF
340340+341341+ $ sh test.sh
342342+ FOO is
343343+ BAR is
344344+ $ msh test.sh
345345+ FOO is
346346+ BAR is
347347+348348+ $ cat > test.sh << EOF
349349+ > echo hello > hello.txt
350350+ > echo world >> hello.txt
351351+ > echo finished >> hello.txt
352352+ >
353353+ > while read line; do
354354+ > echo "Got line: \$line"
355355+ > done < hello.txt
356356+ > EOF
357357+358358+ $ sh test.sh
359359+ Got line: hello
360360+ Got line: world
361361+ Got line: finished
362362+ $ msh test.sh
363363+ Got line: hello
364364+ Got line: world
365365+ Got line: finished