help-bash
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: printf '%s\n' "$@" versus <<< redirection


From: Kerin Millar
Subject: Re: printf '%s\n' "$@" versus <<< redirection
Date: Sat, 18 Feb 2023 10:09:09 +0000

On Sat, 18 Feb 2023 08:50:02 +0000
goncholden <goncholden@protonmail.com> wrote:

> I am using the following bash function 
> 
> theone ()
>  {
>   printf '%s\n' "$@"  \
>     | while IFS="" read -r vl; do
>       ...
>       done
>  }
> 
> I have also been looking at this second implementation
> 
> theone ()
>  {
>    while IFS="" read -r vl; do
>       ...
>    done <<< "$@"
>  }
> 
> But it occurs to me that the two are actually different.  Using <<< means 
> reading from stdin, 
> which will not preserve the arguments, so special chars (like newline) may 
> cause troubles.

Clearly, they do different things but the second sentence is confused. Firstly, 
the reason that incorporating a newline character into the values of any of the 
parameters might cause a problem is because read defaults to using the newline 
as a record (line) delimiter. That particular problem can be circumvented by 
using NUL as a delimiter instead. It serves as a safe delimiter because bash 
strings can never contain it.

  printf '%s\0' "$@" | while IFS= read -rd '' vl; do ...; done

Secondly, and regarding your herestring example, "$@" normally expands to as 
many distinct words as there are positional parameters. However, a herestring 
always expects to consume just _one_ word. In this case, bash chooses to handle 
the situation by joining the positional parameters with the space character, 
producing that word. Should you ever genuinely want to do this, it would make 
more sense to write "$*", in which case the parameters are joined by the first 
character of IFS (by default, a space). The behaviour of "$*" is always the 
same, no matter the context in which it is used. Therefore, it would better 
indicate to the reader that the intention to join the parameters is deliberate.

A better analogue of the first shown function would have involved the use of a 
process substitution, which is another way of setting up a pipe.

  while IFS= read -r vl; do ...; done < <(printf '%s\n' "$@")

Still, I can only assume that the first example is academic in nature, because 
a simple for loop ought to be used instead.

  for vl in "$@"; do ...; done

> 
> The first implementation honours newlines in the arguments, whilst also 
> introduces a newline
> between arguments (between $1, $2, $3, etc).  Am I missing anything in my 
> analysis ?

Apart from that which has been stated above, a herestring always used to result 
in the creation of a temporary file. This behaviour changed with the release of 
5.1. Now, bash may choose to handle a herestring by internally creating a pipe, 
provided that the length of the word is within the limit defined by PIPE_BUF. 
Taking Linux as an example, the default pipe buffer size is 16 times the 
platform's page size. For an x86_64 system, that would amount to 64 KiB.

$ declare -p BASH_VERSION
declare -- BASH_VERSION="5.1.16(1)-release"
$ readlink /proc/self/fd/0 <<<"$(printf %65535s)" # off-by-one because 
herestrings add a newline
pipe:[1276109]
$ readlink /proc/self/fd/0 <<<"$(printf %65536s)" # ditto
/tmp/sh-thd.zpxDpj (deleted)

-- 
Kerin Millar



reply via email to

[Prev in Thread] Current Thread [Next in Thread]