initiate () {
read -p \"Location(s) to look for .bsp files in? \" loc
find $loc -name \"*.bsp\" | while read
do
if [ -f \"$loc.bz2\" ]
then
continue
if you use bash
while read
do
if [ -f "$REPLY.bz2" ]
then
continue
else
filcount=$[$filcount+1]
bzip $REPLY
fi
if [ "$scan" == "1" ]; then bzipint $REPLY
fi
echo $filcount #Correct counting
echo $zipcount #Correct counting
echo $scacount #Correct counting
echo $valid #Equal to 1
done < <(find $loc -name "*.bsp")
To summarize options for using read
at the end of [the conceptual equivalent of] a pipeline in POSIX-like shells:
To recap: in bash by default and in strictly POSIX-compliant shells always, all commands in a pipeline run in a subshell, so variables they create or modify won't be visible to the current shell (won't exist after the pipeline ends).
The following covers bash
, ksh
, zsh
, and sh
([mostly] POSIX-features-only shells such as dash
) and shows ways of avoiding the creation of a subshell so as to preserve the variables created / modified by read
.
If no minimum version number is given, assume that even "pretty old" versions support it (the features in question have been around for a long time, but I don't know specifically when they were introduced.
Note that as a [POSIX-compliant] alternative to the solutions below you can always capture a command's output in a [temporary] file, and then feed it to read
as < file
, which also avoids subshells.
ksh
, and zsh
: NO workaround/configuration change needed at all:The read
builtin by default runs in the current shell when used as the last command in pipeline.
Seemingly, ksh
and zsh
by default run any command in the last stage of a pipeline in the current shell.
Observed in ksh 93u+
and zsh 5.0.5
.
If you know specifically in what version this feature was introduced, let me know.
#!/usr/bin/env ksh
#!/usr/bin/env zsh
out= # initialize output variable
# Pipe multiple lines to the `while` loop and collect the values in the output variable.
printf '%s\n' one two three |
while read -r var; do
out+="$var/"
done
echo "$out" # -> 'one/two/three/'
bash 4.2+
: use the lastpipe
shell optionIn bash version 4.2 or higher, turning on shell option lastpipe
causes the last pipeline segment to run in the current shell, allowing read to create variables visible to the current shell.
#!/usr/bin/env bash
shopt -s lastpipe # bash 4.2+: make the last pipeline command run in *current* shell
out=
printf '%s\n' one two three |
while read -r var; do
out+="$var/"
done
echo "$out" # -> 'one/two/three/'
bash
, ksh
, zsh
: use process substitutionLoosely speaking, a process substitution is a way to have a command's output act like a temporary file.
out=
while read -r var; do
out+="$var/"
done < <(printf '%s\n' one two three) # <(...) is the process substitution
echo "$out" # -> 'one/two/three'
bash
, ksh
, zsh
: use a here-string with a command substitutionout=
while read -r var; do
out+="$var/"
done <<< "$(printf '%s\n' one two three)" # <<< is the here-string operator
echo "$out" # -> 'one/two/three'
Note the need to double-quote the command substitution to protect its output from shell expansions.
sh
): use a here-document with a command substitution#!/bin/sh
out=
while read -r var; do
out="$out$var/"
done <<EOF # <<EOF ... EOF is the here-doc
$(printf '%s\n' one two three)
EOF
echo "$out" # -> 'one/two/three'
Note that, by default, you need to place the ending delimiter - EOF
, in this case - at the very beginning of the line, and that no characters must follow it.
I ran into this problem yesterday.
The trouble is that you're doing find $loc -name "*.bsp" | while read
. Because this involves a pipe, the while read
loop can't actually be running in the same bash process as the rest of your script; bash has to spawn off a subprocess so that it can connect the the stdout of find
to the stdin of the while
loop.
This is all very clever, but it means that any variables set in the loop can't be seen after the loop, which totally defeated the whole purpose of the while
loop I was writing.
You can either try to feed input to the loop without using a pipe, or get output from the loop without using variables. I ended up with a horrifying abomination involving both writing to a temporary file AND wrapping the whole loop in $(...)
, like so:
var="$(producer | while read line; do
...
echo "${something}"
done)"
Which got me var set to all the things that had been echoed from the loop. I probably messed up the syntax of that example; I don't have the code I wrote handy at the moment.