Zsh Mailing List Archive
Messages sorted by: Reverse Date, Date, Thread, Author

Re: Fwd: more splitting travails



On Fri, Jan 12, 2024 at 11:09 PM Bart Schaefer
<schaefer@xxxxxxxxxxxxxxxx> wrote:
>
> Yeah, oddly, there's no straightforward way to get an unaltered file
> into a shell variable.  Even
>   read -rd '' < file
> trims off trailing newlines.  The only somewhat-obvious way is to use
>   zmodload zsh/mapfile
>   var=$mapfile[file]
> but up until a recent dev version, on platforms that don't implement
> memmap that just sneakily reverts to $(<file).
>
> I'm expecting Roman or someone to point out a different trick I've forgotten.

The standard trick here is to print an extra character after the
content of the file and then remove it. This works when capturing
stdout of commands, too.

    printf '\n\nA\n\nB\n\n' >file

    # read the FULL file content
    file_content=${"$(<file && print -n .)"%.} || return

    # read the FULL command stdout
    cmd_stdout=${"$(cat file && print -n .)"%.} || return

    typeset -p file_content cmd_stdout

Unfortunately, these constructs require an extra fork. In
performance-sensitive code the best solution is mapfile or sysread.
They have comparable performance but sysread has the advantage of
being able to read from any file descriptor rather than just a file.

    # Reads stdin until EOF and stores all read content in REPLY.
    # On error, leaves REPLY unmodified and returns 1.
    function slurp() {
      emulate -L zsh -o no_multibyte
      zmodload zsh/system || return
      local content
      while true; do
        sysread 'content[$#content+1]' && continue
        (( $? == 5 )) || return
        break
      done
      typeset -g REPLY=$content
    }

    printf '\n\nA\n\nB\n\n' >file

    slurp <file || return
    file_content=$REPLY

    slurp < <(cat file) || return
    cmd_stdout=$REPLY

    typeset -p file_content cmd_stdout

Roman.




Messages sorted by: Reverse Date, Date, Thread, Author