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 useksh
for fun & learning factor, so at least in my casebash
is always available, so I figured portability in this context isn't a great factor. – Sergiy Kolodyazhnyy Nov 1 '17 at 5:42bash
features 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. – Satō Katsura Nov 1 '17 at 6:24