I do fully agree with @ilkkachu here.
But FWIW, to be able to use that read command as part of the for condition, with ksh93 (where that for ((...)) syntax comes from), you could use disciplines:
function read.get {
IFS= read -r line
.sh.value=$(($? == 0))
}
for ((i = 0; read; i++)) {
printf '%5d: %s\n' "$i" "$line"
}
We set the get discipline of the $read variable so that when expanded, the read command is run and $read is expanded to 1 if read was successful or 0 otherwise.
Or a variant using types:
typeset -T read_t=(
typeset value
function get {
IFS= read -r _.value
((.sh.value = $? == 0))
}
)
read_t line
for ((i = 0; line; i++)) {
printf '%5d: %s\n' "$i" "${line.value}"
}
Where the read_t type is a kind of object which when expanded reads a line into theobject.value and expands to 1 if the read was successful, or 0 otherwise.
Or the ${ ...; } form of command substitution:
for ((i = 0; ${ IFS= read -r line; echo "$(($? == 0))";}; i++)) {
printf '%5d: %s\n' "$i" "$line"
}
With zsh, hijacking the dynamic named directory feature:
set -o extendedglob
handle_-read:var()
case $1:$2 in
(d:-read:[a-zA-Z_][a-zA-Z0-9_]#)
IFS= read -r ${2#*:} && reply=('' $#2);;
(*) false;;
esac
zsh_directory_name_functions+=(handle_-read:var)
for ((i = 0; ${#${(D):--read:line}} == 3; i++)) {
printf '%5d: %s\n' "$i" "$line"
}
(not that I would recommend doing that).
That's the only ways I know of where you can have a command executed as part of an arithmetic expressions not in a subshell.
Normal command substitution ($(...) or `...`) can also be used to run commands in an arithmetic expression, but it's done in a subshell, so something like:
for ((i = 0; $(IFS= read -r line; echo "$((!$?))"); i++)) {
printf '%5d: %s\n' "$i" "$line"
}
While it would be valid syntax for bash, it would fail to set the $line variable outside of the subshell.
With zsh, you could however do something like:
for ((i = 0; ${${line::=$(IFS= read -re)}+$?} == 0; i++)) {
printf '%5d: %s\n' "$i" "$line"
}
Though that would be inefficient as it would fork a subshell for each line and feed the line through an extra pipe.
bash doesn't have the ${var::=value} unconditional assignment parameter expansion operator and its ability to nest parameter expansions is very limited. It does have the ${var=value} Bourne operator (assign if previously unset), and there are some operators that allow nesting like ${foo#${bar}}, so you could do something like:
unset line
for ((i = 0; 0*${?#"x${line=`IFS= read -r && printf %s "$REPLY"`}"}+$? == 0; i++)); do
printf '%5d: %s\n' "$i" "$line"
unset line
done
(here having to work around two bugs of bash).
bash, and I usekshfor fun & learning factor, so at least in my casebashis always available, so I figured portability in this context isn't a great factor.bashfeatures you'll need to either installbash, or fix the script. If it's portable you'll just head for a beer. If it costs nothing to do it portably, why not do it right from the beginning. Don't create future problems when you can avoid them for free.