A variable modified inside a while loop is not rememberedPropagate value of variable to outside of the loopBash shell is “clearing” a variable after while loop, how to get all files in a folder inside a variable?Variables set in a bash 'while read' loop are unset after itBash build array in while loop (does not persist)?Variable undefined outside of the loopgetting variable outside loop linux bashCounting in a while loopbash: accessing global variables from a command pipelineShell variable gets modified in 'while' loop, but not in 'find | while' loopUse different variables in one while loop or better use for loop?Using global variables in a functionWhat is the scope of variables in JavaScript?Emulate a do-while loop in Python?What's the difference between using “let” and “var”?Syntax for a single-line Bash infinite while loopHow to declare and use boolean variables in shell script?How to concatenate string variables in BashLoop through an array of strings in Bash?Is there a reason for C#'s reuse of the variable in a foreach?Setting an environment variable before a command in bash not working for second command in a pipe

What does air vanishing on contact sound like?

Is it the same airport YUL and YMQ in Canada?

Meaning of "individuandum"

Was Unix ever a single-user OS?

How do I tell my manager that his code review comment is wrong?

Is balancing necessary on a full-wheel change?

Binary Numbers Magic Trick

Was the ancestor of SCSI, the SASI protocol, nothing more than a draft?

Map one pandas column using two dictionaries

When and why did journal article titles become descriptive, rather than creatively allusive?

How to creep the reader out with what seems like a normal person?

When do aircrafts become solarcrafts?

Floor tile layout process?

How to get SEEK accessing converted ID via view

I’ve officially counted to infinity!

Disabling Resource Governor in SQL Server

How to reply this mail from potential PhD professor?

Packet sniffer for MacOS Mojave and above

How can I fairly adjudicate the effects of height differences on ranged attacks?

If 1. e4 c6 is considered as a sound defense for black, why is 1. c3 so rare?

My ID is expired, can I fly to the Bahamas with my passport

Write to EXCEL from SQL DB using VBA script

What are the spoon bit of a spoon and fork bit of a fork called?

How did Arya manage to disguise herself?



A variable modified inside a while loop is not remembered


Propagate value of variable to outside of the loopBash shell is “clearing” a variable after while loop, how to get all files in a folder inside a variable?Variables set in a bash 'while read' loop are unset after itBash build array in while loop (does not persist)?Variable undefined outside of the loopgetting variable outside loop linux bashCounting in a while loopbash: accessing global variables from a command pipelineShell variable gets modified in 'while' loop, but not in 'find | while' loopUse different variables in one while loop or better use for loop?Using global variables in a functionWhat is the scope of variables in JavaScript?Emulate a do-while loop in Python?What's the difference between using “let” and “var”?Syntax for a single-line Bash infinite while loopHow to declare and use boolean variables in shell script?How to concatenate string variables in BashLoop through an array of strings in Bash?Is there a reason for C#'s reuse of the variable in a foreach?Setting an environment variable before a command in bash not working for second command in a pipe






.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty,.everyoneloves__bot-mid-leaderboard:empty height:90px;width:728px;box-sizing:border-box;








147















In the following program, if I set the variable $foo to the value 1 inside the first if statement, it works in the sense that its value is remembered after the if statement. However, when I set the same variable to the value 2 inside an if which is inside a while statement, it's forgotten after the while loop. It's behaving like I'm using some sort of copy of the variable $foo inside the while loop and I am modifying only that particular copy. Here's a complete test program:



#!/bin/bash

set -e
set -u
foo=0
bar="hello"
if [[ "$bar" == "hello" ]]
then
foo=1
echo "Setting $foo to 1: $foo"
fi

echo "Variable $foo after if statement: $foo"
lines="first linensecond linenthird line"
echo -e $lines | while read line
do
if [[ "$line" == "second line" ]]
then
foo=2
echo "Variable $foo updated to $foo inside if inside while loop"
fi
echo "Value of $foo in while loop body: $foo"
done

echo "Variable $foo after while loop: $foo"

# Output:
# $ ./testbash.sh
# Setting $foo to 1: 1
# Variable $foo after if statement: 1
# Value of $foo in while loop body: 1
# Variable $foo updated to 2 inside if inside while loop
# Value of $foo in while loop body: 2
# Value of $foo in while loop body: 2
# Variable $foo after while loop: 1

# bash --version
# GNU bash, version 4.1.10(4)-release (i686-pc-cygwin)









share|improve this question



















  • 1





    Key reading here: I set variables in a loop that's in a pipeline. Why do they disappear after the loop terminates? Or, why can't I pipe data to read?.

    – fedorqui
    Dec 11 '15 at 11:48











  • The shellcheck utility catches this (see github.com/koalaman/shellcheck/wiki/SC2030); Cut and pasting the above code in shellcheck.net issues this feedback for Line 19: SC2030: Modification of foo is local (to subshell caused by pipeline).

    – qneill
    Jun 21 '18 at 19:01


















147















In the following program, if I set the variable $foo to the value 1 inside the first if statement, it works in the sense that its value is remembered after the if statement. However, when I set the same variable to the value 2 inside an if which is inside a while statement, it's forgotten after the while loop. It's behaving like I'm using some sort of copy of the variable $foo inside the while loop and I am modifying only that particular copy. Here's a complete test program:



#!/bin/bash

set -e
set -u
foo=0
bar="hello"
if [[ "$bar" == "hello" ]]
then
foo=1
echo "Setting $foo to 1: $foo"
fi

echo "Variable $foo after if statement: $foo"
lines="first linensecond linenthird line"
echo -e $lines | while read line
do
if [[ "$line" == "second line" ]]
then
foo=2
echo "Variable $foo updated to $foo inside if inside while loop"
fi
echo "Value of $foo in while loop body: $foo"
done

echo "Variable $foo after while loop: $foo"

# Output:
# $ ./testbash.sh
# Setting $foo to 1: 1
# Variable $foo after if statement: 1
# Value of $foo in while loop body: 1
# Variable $foo updated to 2 inside if inside while loop
# Value of $foo in while loop body: 2
# Value of $foo in while loop body: 2
# Variable $foo after while loop: 1

# bash --version
# GNU bash, version 4.1.10(4)-release (i686-pc-cygwin)









share|improve this question



















  • 1





    Key reading here: I set variables in a loop that's in a pipeline. Why do they disappear after the loop terminates? Or, why can't I pipe data to read?.

    – fedorqui
    Dec 11 '15 at 11:48











  • The shellcheck utility catches this (see github.com/koalaman/shellcheck/wiki/SC2030); Cut and pasting the above code in shellcheck.net issues this feedback for Line 19: SC2030: Modification of foo is local (to subshell caused by pipeline).

    – qneill
    Jun 21 '18 at 19:01














147












147








147


36






In the following program, if I set the variable $foo to the value 1 inside the first if statement, it works in the sense that its value is remembered after the if statement. However, when I set the same variable to the value 2 inside an if which is inside a while statement, it's forgotten after the while loop. It's behaving like I'm using some sort of copy of the variable $foo inside the while loop and I am modifying only that particular copy. Here's a complete test program:



#!/bin/bash

set -e
set -u
foo=0
bar="hello"
if [[ "$bar" == "hello" ]]
then
foo=1
echo "Setting $foo to 1: $foo"
fi

echo "Variable $foo after if statement: $foo"
lines="first linensecond linenthird line"
echo -e $lines | while read line
do
if [[ "$line" == "second line" ]]
then
foo=2
echo "Variable $foo updated to $foo inside if inside while loop"
fi
echo "Value of $foo in while loop body: $foo"
done

echo "Variable $foo after while loop: $foo"

# Output:
# $ ./testbash.sh
# Setting $foo to 1: 1
# Variable $foo after if statement: 1
# Value of $foo in while loop body: 1
# Variable $foo updated to 2 inside if inside while loop
# Value of $foo in while loop body: 2
# Value of $foo in while loop body: 2
# Variable $foo after while loop: 1

# bash --version
# GNU bash, version 4.1.10(4)-release (i686-pc-cygwin)









share|improve this question
















In the following program, if I set the variable $foo to the value 1 inside the first if statement, it works in the sense that its value is remembered after the if statement. However, when I set the same variable to the value 2 inside an if which is inside a while statement, it's forgotten after the while loop. It's behaving like I'm using some sort of copy of the variable $foo inside the while loop and I am modifying only that particular copy. Here's a complete test program:



#!/bin/bash

set -e
set -u
foo=0
bar="hello"
if [[ "$bar" == "hello" ]]
then
foo=1
echo "Setting $foo to 1: $foo"
fi

echo "Variable $foo after if statement: $foo"
lines="first linensecond linenthird line"
echo -e $lines | while read line
do
if [[ "$line" == "second line" ]]
then
foo=2
echo "Variable $foo updated to $foo inside if inside while loop"
fi
echo "Value of $foo in while loop body: $foo"
done

echo "Variable $foo after while loop: $foo"

# Output:
# $ ./testbash.sh
# Setting $foo to 1: 1
# Variable $foo after if statement: 1
# Value of $foo in while loop body: 1
# Variable $foo updated to 2 inside if inside while loop
# Value of $foo in while loop body: 2
# Value of $foo in while loop body: 2
# Variable $foo after while loop: 1

# bash --version
# GNU bash, version 4.1.10(4)-release (i686-pc-cygwin)






bash while-loop scope sh






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Feb 14 at 12:45









TradingDerivatives.eu

55




55










asked May 31 '13 at 9:38









Eric LiljaEric Lilja

9163911




9163911







  • 1





    Key reading here: I set variables in a loop that's in a pipeline. Why do they disappear after the loop terminates? Or, why can't I pipe data to read?.

    – fedorqui
    Dec 11 '15 at 11:48











  • The shellcheck utility catches this (see github.com/koalaman/shellcheck/wiki/SC2030); Cut and pasting the above code in shellcheck.net issues this feedback for Line 19: SC2030: Modification of foo is local (to subshell caused by pipeline).

    – qneill
    Jun 21 '18 at 19:01













  • 1





    Key reading here: I set variables in a loop that's in a pipeline. Why do they disappear after the loop terminates? Or, why can't I pipe data to read?.

    – fedorqui
    Dec 11 '15 at 11:48











  • The shellcheck utility catches this (see github.com/koalaman/shellcheck/wiki/SC2030); Cut and pasting the above code in shellcheck.net issues this feedback for Line 19: SC2030: Modification of foo is local (to subshell caused by pipeline).

    – qneill
    Jun 21 '18 at 19:01








1




1





Key reading here: I set variables in a loop that's in a pipeline. Why do they disappear after the loop terminates? Or, why can't I pipe data to read?.

– fedorqui
Dec 11 '15 at 11:48





Key reading here: I set variables in a loop that's in a pipeline. Why do they disappear after the loop terminates? Or, why can't I pipe data to read?.

– fedorqui
Dec 11 '15 at 11:48













The shellcheck utility catches this (see github.com/koalaman/shellcheck/wiki/SC2030); Cut and pasting the above code in shellcheck.net issues this feedback for Line 19: SC2030: Modification of foo is local (to subshell caused by pipeline).

– qneill
Jun 21 '18 at 19:01






The shellcheck utility catches this (see github.com/koalaman/shellcheck/wiki/SC2030); Cut and pasting the above code in shellcheck.net issues this feedback for Line 19: SC2030: Modification of foo is local (to subshell caused by pipeline).

– qneill
Jun 21 '18 at 19:01













6 Answers
6






active

oldest

votes


















190














echo -e $lines | while read line 
...
done


The while loop is executed in a subshell. So any changes you do to the variable will not be available once the subshell exits.



Instead you can use a here string to re-write the while loop to be in the main shell process; only echo -e $lines will run in a subshell:



while read line
do
if [[ "$line" == "second line" ]]
then
foo=2
echo "Variable $foo updated to $foo inside if inside while loop"
fi
echo "Value of $foo in while loop body: $foo"
done <<< "$(echo -e "$lines")"


You can get rid of the rather ugly echo in the here-string above by expanding the backslash sequences immediately when assigning lines. The $'...' form of quoting can be used there:



lines=$'first linensecond linenthird line'
while read line; do
...
done <<< "$lines"





share|improve this answer




















  • 13





    better change <<< "$(echo -e "$lines")" to simple <<< "$lines"

    – beliy
    May 18 '17 at 14:43











  • what if the source was from tail -f instead of fixed text?

    – mt eee
    Nov 9 '18 at 7:57






  • 1





    @mteee You can use while read -r line; do echo "LINE: $line"; done < <(tail -f file) (obviously the loop won't terminate as it continues to wait for input from tail).

    – P.P.
    Nov 10 '18 at 16:42


















45














UPDATED#2



Explanation is in Blue Moons's answer.



Alternative solutions:



Eliminate echo



while read line; do
...
done <<EOT
first line
second line
third line
EOT


Add the echo inside the here-is-the-document



while read line; do
...
done <<EOT
$(echo -e $lines)
EOT


Run echo in background:



coproc echo -e $lines
while read -u $COPROC[0] line; do
...
done


Redirect to a file handle explicitly (Mind the space in < <!):



exec 3< <(echo -e $lines)
while read -u 3 line; do
...
done


Or just redirect to the stdin:



while read line; do
...
done < <(echo -e $lines)


And one for chepner (eliminating echo):



arr=("first line" "second line" "third line");
for((i=0;i<$#arr[*];++i)) line=$arr[i];
...



Variable $lines can be converted to an array without starting a new sub-shell. The characters and n has to be converted to some character (e.g. a real new line character) and use the IFS (Internal Field Separator) variable to split the string into array elements. This can be done like:



lines="first linensecond linenthird line"
echo "$lines"
OIFS="$IFS"
IFS=$'n' arr=($lines//\n/$'n') # Conversion
IFS="$OIFS"
echo "$arr[@]", Length: $#arr[*]
set|grep ^arr


Result is



first linensecond linenthird line
first line second line third line, Length: 3
arr=([0]="first line" [1]="second line" [2]="third line")





share|improve this answer

























  • +1 for the here-doc, since the lines variable's only purpose seems to be to feed the while loop.

    – chepner
    May 31 '13 at 12:44











  • @chepner: Thx! I added another one, dedicated to You!

    – TrueY
    May 31 '13 at 12:57











  • There is yet another solution given here: for line in $(echo -e $lines); do ... done

    – dma_k
    Jan 19 '16 at 20:20











  • @dma_k Thanks for your comment! This solution would result 6 lines containing a single word. OP's request was different...

    – TrueY
    Jan 20 '16 at 8:59











  • upvoted. running echo in a subshell inside here-is, was one of the few solutions that worked in ash

    – Hamy
    Nov 26 '16 at 5:03


















9














You are the 742342nd user to ask this bash FAQ. The answer also describes the general case of variables set in subshells created by pipes:




E4) If I pipe the output of a command into read variable, why
doesn't the output show up in $variable when the read command finishes?



This has to do with the parent-child relationship between Unix
processes. It affects all commands run in pipelines, not just
simple calls to read. For example, piping a command's output
into a while loop that repeatedly calls read will result in
the same behavior.



Each element of a pipeline, even a builtin or shell function,
runs in a separate process, a child of the shell running the
pipeline. A subprocess cannot affect its parent's environment.
When the read command sets the variable to the input, that
variable is set only in the subshell, not the parent shell. When
the subshell exits, the value of the variable is lost.



Many pipelines that end with read variable can be converted
into command substitutions, which will capture the output of
a specified command. The output can then be assigned to a
variable:



grep ^gnu /usr/lib/news/active | wc -l | read ngroup


can be converted into



ngroup=$(grep ^gnu /usr/lib/news/active | wc -l)


This does not, unfortunately, work to split the text among
multiple variables, as read does when given multiple variable
arguments. If you need to do this, you can either use the
command substitution above to read the output into a variable
and chop up the variable using the bash pattern removal
expansion operators or use some variant of the following
approach.



Say /usr/local/bin/ipaddr is the following shell script:



#! /bin/sh
host `hostname` | awk '/address/ print $NF'


Instead of using



/usr/local/bin/ipaddr | read A B C D


to break the local machine's IP address into separate octets, use



OIFS="$IFS"
IFS=.
set -- $(/usr/local/bin/ipaddr)
IFS="$OIFS"
A="$1" B="$2" C="$3" D="$4"


Beware, however, that this will change the shell's positional
parameters. If you need them, you should save them before doing
this.



This is the general approach -- in most cases you will not need to
set $IFS to a different value.



Some other user-supplied alternatives include:



read A B C D << HERE
$(IFS=.; echo $(/usr/local/bin/ipaddr))
HERE


and, where process substitution is available,



read A B C D < <(IFS=.; echo $(/usr/local/bin/ipaddr))






share|improve this answer




















  • 5





    You forgot the Family Feud problem. Sometimes it’s very hard to find the same combination of words as whoever wrote the answer, so that you’re neither flooded in wrong results, nor filter out said answer.

    – Evi1M4chine
    Jan 29 '16 at 19:20


















1














Hmmm... I would almost swear that this worked for the original Bourne shell, but don't have access to a running copy just now to check.



There is, however, a very trivial workaround to the problem.



Change the first line of the script from:



#!/bin/bash


to



#!/bin/ksh


Et voila! A read at the end of a pipeline works just fine, assuming you have the Korn shell installed.






share|improve this answer






























    0














    This is an interesting question and touch a very basic concept in Bourne shell and subshell. Here I provide a solution that is different from the previous solutions by doing some kind of filtering. I will give an example that may be useful in real life. This is a fragment for checking the downloaded files conform to know checksums. The checksum file look like the following (Showing just 3 lines):



    49174 36326 dna_align_feature.txt.gz
    54757 1 dna.txt.gz
    55409 9971 exon_transcript.txt.gz


    The shell script:



    #!/bin/sh

    .....

    failcnt=0 # this variable is only valid in the parent shell
    #variable xx captures all the outputs from the while loop
    xx=$(cat $checkfile | while read -r line; do
    num1=$(echo $line | awk 'print $1')
    num2=$(echo $line | awk 'print $2')
    fname=$(echo $line | awk 'print $3')
    if [ -f "$fname" ]; then
    res=$(sum $fname)
    filegood=$(sum $fname | awk -v na=$num1 -v nb=$num2 -v fn=$fname ' if (na == $1 && nb == $2) print "TRUE"; else print "FALSE"; ')
    if [ "$filegood" = "FALSE" ]; then
    failcnt=$(expr $failcnt + 1) # only in subshell
    echo "$fname BAD $failcnt"
    fi
    fi
    done | tail -1) # I am only interested in the final result
    # you can capture a whole bunch of texts and do further filtering
    failcnt=$xx#* BAD # I am only interested in the number
    # this variable is in the parent shell
    echo failcnt $failcnt
    if [ $failcnt -gt 0 ]; then
    echo $failcnt files failed
    else
    echo download successful
    fi


    The parent and subshell communicate through the echo command. You can pick some easy to parse text for the parent shell. This method does not break your normal way of thinking, just that you have to do some post processing. You can use grep, sed, awk, and more for doing so.






    share|improve this answer






























      -1














      How about a very simple method



       +call your while loop in a function 
      - set your value inside (nonsense, but shows the example)
      - return your value inside
      +capture your value outside
      +set outside
      +display outside


      #!/bin/bash
      # set -e
      # set -u
      # No idea why you need this, not using here

      foo=0
      bar="hello"

      if [[ "$bar" == "hello" ]]
      then
      foo=1
      echo "Setting $foo to $foo"
      fi

      echo "Variable $foo after if statement: $foo"

      lines="first linensecond linenthird line"

      function my_while_loop


      echo -e $lines

      my_while_loop; foo="$?"

      echo "Variable $foo after while loop: $foo"


      Output:
      Setting $foo 1
      Variable $foo after if statement: 1
      Value of $foo in while loop body: 1
      Variable $foo after while loop: 2

      bash --version

      GNU bash, version 3.2.51(1)-release (x86_64-apple-darwin13)
      Copyright (C) 2007 Free Software Foundation, Inc.





      share|improve this answer




















      • 6





        Maybe there's a decent answer under the surface here, but you've butchered the formatting to the point that it's unpleasant to try and read.

        – Mark Amery
        Sep 10 '16 at 19:42











      • You mean the original code is pleasant to read? (I've just followed :p)

        – Marcin
        Aug 11 '17 at 14:20











      Your Answer






      StackExchange.ifUsing("editor", function ()
      StackExchange.using("externalEditor", function ()
      StackExchange.using("snippets", function ()
      StackExchange.snippets.init();
      );
      );
      , "code-snippets");

      StackExchange.ready(function()
      var channelOptions =
      tags: "".split(" "),
      id: "1"
      ;
      initTagRenderer("".split(" "), "".split(" "), channelOptions);

      StackExchange.using("externalEditor", function()
      // Have to fire editor after snippets, if snippets enabled
      if (StackExchange.settings.snippets.snippetsEnabled)
      StackExchange.using("snippets", function()
      createEditor();
      );

      else
      createEditor();

      );

      function createEditor()
      StackExchange.prepareEditor(
      heartbeatType: 'answer',
      autoActivateHeartbeat: false,
      convertImagesToLinks: true,
      noModals: true,
      showLowRepImageUploadWarning: true,
      reputationToPostImages: 10,
      bindNavPrevention: true,
      postfix: "",
      imageUploader:
      brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
      contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
      allowUrls: true
      ,
      onDemand: true,
      discardSelector: ".discard-answer"
      ,immediatelyShowMarkdownHelp:true
      );



      );













      draft saved

      draft discarded


















      StackExchange.ready(
      function ()
      StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f16854280%2fa-variable-modified-inside-a-while-loop-is-not-remembered%23new-answer', 'question_page');

      );

      Post as a guest















      Required, but never shown

























      6 Answers
      6






      active

      oldest

      votes








      6 Answers
      6






      active

      oldest

      votes









      active

      oldest

      votes






      active

      oldest

      votes









      190














      echo -e $lines | while read line 
      ...
      done


      The while loop is executed in a subshell. So any changes you do to the variable will not be available once the subshell exits.



      Instead you can use a here string to re-write the while loop to be in the main shell process; only echo -e $lines will run in a subshell:



      while read line
      do
      if [[ "$line" == "second line" ]]
      then
      foo=2
      echo "Variable $foo updated to $foo inside if inside while loop"
      fi
      echo "Value of $foo in while loop body: $foo"
      done <<< "$(echo -e "$lines")"


      You can get rid of the rather ugly echo in the here-string above by expanding the backslash sequences immediately when assigning lines. The $'...' form of quoting can be used there:



      lines=$'first linensecond linenthird line'
      while read line; do
      ...
      done <<< "$lines"





      share|improve this answer




















      • 13





        better change <<< "$(echo -e "$lines")" to simple <<< "$lines"

        – beliy
        May 18 '17 at 14:43











      • what if the source was from tail -f instead of fixed text?

        – mt eee
        Nov 9 '18 at 7:57






      • 1





        @mteee You can use while read -r line; do echo "LINE: $line"; done < <(tail -f file) (obviously the loop won't terminate as it continues to wait for input from tail).

        – P.P.
        Nov 10 '18 at 16:42















      190














      echo -e $lines | while read line 
      ...
      done


      The while loop is executed in a subshell. So any changes you do to the variable will not be available once the subshell exits.



      Instead you can use a here string to re-write the while loop to be in the main shell process; only echo -e $lines will run in a subshell:



      while read line
      do
      if [[ "$line" == "second line" ]]
      then
      foo=2
      echo "Variable $foo updated to $foo inside if inside while loop"
      fi
      echo "Value of $foo in while loop body: $foo"
      done <<< "$(echo -e "$lines")"


      You can get rid of the rather ugly echo in the here-string above by expanding the backslash sequences immediately when assigning lines. The $'...' form of quoting can be used there:



      lines=$'first linensecond linenthird line'
      while read line; do
      ...
      done <<< "$lines"





      share|improve this answer




















      • 13





        better change <<< "$(echo -e "$lines")" to simple <<< "$lines"

        – beliy
        May 18 '17 at 14:43











      • what if the source was from tail -f instead of fixed text?

        – mt eee
        Nov 9 '18 at 7:57






      • 1





        @mteee You can use while read -r line; do echo "LINE: $line"; done < <(tail -f file) (obviously the loop won't terminate as it continues to wait for input from tail).

        – P.P.
        Nov 10 '18 at 16:42













      190












      190








      190







      echo -e $lines | while read line 
      ...
      done


      The while loop is executed in a subshell. So any changes you do to the variable will not be available once the subshell exits.



      Instead you can use a here string to re-write the while loop to be in the main shell process; only echo -e $lines will run in a subshell:



      while read line
      do
      if [[ "$line" == "second line" ]]
      then
      foo=2
      echo "Variable $foo updated to $foo inside if inside while loop"
      fi
      echo "Value of $foo in while loop body: $foo"
      done <<< "$(echo -e "$lines")"


      You can get rid of the rather ugly echo in the here-string above by expanding the backslash sequences immediately when assigning lines. The $'...' form of quoting can be used there:



      lines=$'first linensecond linenthird line'
      while read line; do
      ...
      done <<< "$lines"





      share|improve this answer















      echo -e $lines | while read line 
      ...
      done


      The while loop is executed in a subshell. So any changes you do to the variable will not be available once the subshell exits.



      Instead you can use a here string to re-write the while loop to be in the main shell process; only echo -e $lines will run in a subshell:



      while read line
      do
      if [[ "$line" == "second line" ]]
      then
      foo=2
      echo "Variable $foo updated to $foo inside if inside while loop"
      fi
      echo "Value of $foo in while loop body: $foo"
      done <<< "$(echo -e "$lines")"


      You can get rid of the rather ugly echo in the here-string above by expanding the backslash sequences immediately when assigning lines. The $'...' form of quoting can be used there:



      lines=$'first linensecond linenthird line'
      while read line; do
      ...
      done <<< "$lines"






      share|improve this answer














      share|improve this answer



      share|improve this answer








      edited Jan 2 at 21:19









      ilkkachu

      3,665320




      3,665320










      answered May 31 '13 at 9:40









      P.P.P.P.

      76.3k12111158




      76.3k12111158







      • 13





        better change <<< "$(echo -e "$lines")" to simple <<< "$lines"

        – beliy
        May 18 '17 at 14:43











      • what if the source was from tail -f instead of fixed text?

        – mt eee
        Nov 9 '18 at 7:57






      • 1





        @mteee You can use while read -r line; do echo "LINE: $line"; done < <(tail -f file) (obviously the loop won't terminate as it continues to wait for input from tail).

        – P.P.
        Nov 10 '18 at 16:42












      • 13





        better change <<< "$(echo -e "$lines")" to simple <<< "$lines"

        – beliy
        May 18 '17 at 14:43











      • what if the source was from tail -f instead of fixed text?

        – mt eee
        Nov 9 '18 at 7:57






      • 1





        @mteee You can use while read -r line; do echo "LINE: $line"; done < <(tail -f file) (obviously the loop won't terminate as it continues to wait for input from tail).

        – P.P.
        Nov 10 '18 at 16:42







      13




      13





      better change <<< "$(echo -e "$lines")" to simple <<< "$lines"

      – beliy
      May 18 '17 at 14:43





      better change <<< "$(echo -e "$lines")" to simple <<< "$lines"

      – beliy
      May 18 '17 at 14:43













      what if the source was from tail -f instead of fixed text?

      – mt eee
      Nov 9 '18 at 7:57





      what if the source was from tail -f instead of fixed text?

      – mt eee
      Nov 9 '18 at 7:57




      1




      1





      @mteee You can use while read -r line; do echo "LINE: $line"; done < <(tail -f file) (obviously the loop won't terminate as it continues to wait for input from tail).

      – P.P.
      Nov 10 '18 at 16:42





      @mteee You can use while read -r line; do echo "LINE: $line"; done < <(tail -f file) (obviously the loop won't terminate as it continues to wait for input from tail).

      – P.P.
      Nov 10 '18 at 16:42













      45














      UPDATED#2



      Explanation is in Blue Moons's answer.



      Alternative solutions:



      Eliminate echo



      while read line; do
      ...
      done <<EOT
      first line
      second line
      third line
      EOT


      Add the echo inside the here-is-the-document



      while read line; do
      ...
      done <<EOT
      $(echo -e $lines)
      EOT


      Run echo in background:



      coproc echo -e $lines
      while read -u $COPROC[0] line; do
      ...
      done


      Redirect to a file handle explicitly (Mind the space in < <!):



      exec 3< <(echo -e $lines)
      while read -u 3 line; do
      ...
      done


      Or just redirect to the stdin:



      while read line; do
      ...
      done < <(echo -e $lines)


      And one for chepner (eliminating echo):



      arr=("first line" "second line" "third line");
      for((i=0;i<$#arr[*];++i)) line=$arr[i];
      ...



      Variable $lines can be converted to an array without starting a new sub-shell. The characters and n has to be converted to some character (e.g. a real new line character) and use the IFS (Internal Field Separator) variable to split the string into array elements. This can be done like:



      lines="first linensecond linenthird line"
      echo "$lines"
      OIFS="$IFS"
      IFS=$'n' arr=($lines//\n/$'n') # Conversion
      IFS="$OIFS"
      echo "$arr[@]", Length: $#arr[*]
      set|grep ^arr


      Result is



      first linensecond linenthird line
      first line second line third line, Length: 3
      arr=([0]="first line" [1]="second line" [2]="third line")





      share|improve this answer

























      • +1 for the here-doc, since the lines variable's only purpose seems to be to feed the while loop.

        – chepner
        May 31 '13 at 12:44











      • @chepner: Thx! I added another one, dedicated to You!

        – TrueY
        May 31 '13 at 12:57











      • There is yet another solution given here: for line in $(echo -e $lines); do ... done

        – dma_k
        Jan 19 '16 at 20:20











      • @dma_k Thanks for your comment! This solution would result 6 lines containing a single word. OP's request was different...

        – TrueY
        Jan 20 '16 at 8:59











      • upvoted. running echo in a subshell inside here-is, was one of the few solutions that worked in ash

        – Hamy
        Nov 26 '16 at 5:03















      45














      UPDATED#2



      Explanation is in Blue Moons's answer.



      Alternative solutions:



      Eliminate echo



      while read line; do
      ...
      done <<EOT
      first line
      second line
      third line
      EOT


      Add the echo inside the here-is-the-document



      while read line; do
      ...
      done <<EOT
      $(echo -e $lines)
      EOT


      Run echo in background:



      coproc echo -e $lines
      while read -u $COPROC[0] line; do
      ...
      done


      Redirect to a file handle explicitly (Mind the space in < <!):



      exec 3< <(echo -e $lines)
      while read -u 3 line; do
      ...
      done


      Or just redirect to the stdin:



      while read line; do
      ...
      done < <(echo -e $lines)


      And one for chepner (eliminating echo):



      arr=("first line" "second line" "third line");
      for((i=0;i<$#arr[*];++i)) line=$arr[i];
      ...



      Variable $lines can be converted to an array without starting a new sub-shell. The characters and n has to be converted to some character (e.g. a real new line character) and use the IFS (Internal Field Separator) variable to split the string into array elements. This can be done like:



      lines="first linensecond linenthird line"
      echo "$lines"
      OIFS="$IFS"
      IFS=$'n' arr=($lines//\n/$'n') # Conversion
      IFS="$OIFS"
      echo "$arr[@]", Length: $#arr[*]
      set|grep ^arr


      Result is



      first linensecond linenthird line
      first line second line third line, Length: 3
      arr=([0]="first line" [1]="second line" [2]="third line")





      share|improve this answer

























      • +1 for the here-doc, since the lines variable's only purpose seems to be to feed the while loop.

        – chepner
        May 31 '13 at 12:44











      • @chepner: Thx! I added another one, dedicated to You!

        – TrueY
        May 31 '13 at 12:57











      • There is yet another solution given here: for line in $(echo -e $lines); do ... done

        – dma_k
        Jan 19 '16 at 20:20











      • @dma_k Thanks for your comment! This solution would result 6 lines containing a single word. OP's request was different...

        – TrueY
        Jan 20 '16 at 8:59











      • upvoted. running echo in a subshell inside here-is, was one of the few solutions that worked in ash

        – Hamy
        Nov 26 '16 at 5:03













      45












      45








      45







      UPDATED#2



      Explanation is in Blue Moons's answer.



      Alternative solutions:



      Eliminate echo



      while read line; do
      ...
      done <<EOT
      first line
      second line
      third line
      EOT


      Add the echo inside the here-is-the-document



      while read line; do
      ...
      done <<EOT
      $(echo -e $lines)
      EOT


      Run echo in background:



      coproc echo -e $lines
      while read -u $COPROC[0] line; do
      ...
      done


      Redirect to a file handle explicitly (Mind the space in < <!):



      exec 3< <(echo -e $lines)
      while read -u 3 line; do
      ...
      done


      Or just redirect to the stdin:



      while read line; do
      ...
      done < <(echo -e $lines)


      And one for chepner (eliminating echo):



      arr=("first line" "second line" "third line");
      for((i=0;i<$#arr[*];++i)) line=$arr[i];
      ...



      Variable $lines can be converted to an array without starting a new sub-shell. The characters and n has to be converted to some character (e.g. a real new line character) and use the IFS (Internal Field Separator) variable to split the string into array elements. This can be done like:



      lines="first linensecond linenthird line"
      echo "$lines"
      OIFS="$IFS"
      IFS=$'n' arr=($lines//\n/$'n') # Conversion
      IFS="$OIFS"
      echo "$arr[@]", Length: $#arr[*]
      set|grep ^arr


      Result is



      first linensecond linenthird line
      first line second line third line, Length: 3
      arr=([0]="first line" [1]="second line" [2]="third line")





      share|improve this answer















      UPDATED#2



      Explanation is in Blue Moons's answer.



      Alternative solutions:



      Eliminate echo



      while read line; do
      ...
      done <<EOT
      first line
      second line
      third line
      EOT


      Add the echo inside the here-is-the-document



      while read line; do
      ...
      done <<EOT
      $(echo -e $lines)
      EOT


      Run echo in background:



      coproc echo -e $lines
      while read -u $COPROC[0] line; do
      ...
      done


      Redirect to a file handle explicitly (Mind the space in < <!):



      exec 3< <(echo -e $lines)
      while read -u 3 line; do
      ...
      done


      Or just redirect to the stdin:



      while read line; do
      ...
      done < <(echo -e $lines)


      And one for chepner (eliminating echo):



      arr=("first line" "second line" "third line");
      for((i=0;i<$#arr[*];++i)) line=$arr[i];
      ...



      Variable $lines can be converted to an array without starting a new sub-shell. The characters and n has to be converted to some character (e.g. a real new line character) and use the IFS (Internal Field Separator) variable to split the string into array elements. This can be done like:



      lines="first linensecond linenthird line"
      echo "$lines"
      OIFS="$IFS"
      IFS=$'n' arr=($lines//\n/$'n') # Conversion
      IFS="$OIFS"
      echo "$arr[@]", Length: $#arr[*]
      set|grep ^arr


      Result is



      first linensecond linenthird line
      first line second line third line, Length: 3
      arr=([0]="first line" [1]="second line" [2]="third line")






      share|improve this answer














      share|improve this answer



      share|improve this answer








      edited Oct 30 '15 at 13:50

























      answered May 31 '13 at 10:28









      TrueYTrueY

      5,97912839




      5,97912839












      • +1 for the here-doc, since the lines variable's only purpose seems to be to feed the while loop.

        – chepner
        May 31 '13 at 12:44











      • @chepner: Thx! I added another one, dedicated to You!

        – TrueY
        May 31 '13 at 12:57











      • There is yet another solution given here: for line in $(echo -e $lines); do ... done

        – dma_k
        Jan 19 '16 at 20:20











      • @dma_k Thanks for your comment! This solution would result 6 lines containing a single word. OP's request was different...

        – TrueY
        Jan 20 '16 at 8:59











      • upvoted. running echo in a subshell inside here-is, was one of the few solutions that worked in ash

        – Hamy
        Nov 26 '16 at 5:03

















      • +1 for the here-doc, since the lines variable's only purpose seems to be to feed the while loop.

        – chepner
        May 31 '13 at 12:44











      • @chepner: Thx! I added another one, dedicated to You!

        – TrueY
        May 31 '13 at 12:57











      • There is yet another solution given here: for line in $(echo -e $lines); do ... done

        – dma_k
        Jan 19 '16 at 20:20











      • @dma_k Thanks for your comment! This solution would result 6 lines containing a single word. OP's request was different...

        – TrueY
        Jan 20 '16 at 8:59











      • upvoted. running echo in a subshell inside here-is, was one of the few solutions that worked in ash

        – Hamy
        Nov 26 '16 at 5:03
















      +1 for the here-doc, since the lines variable's only purpose seems to be to feed the while loop.

      – chepner
      May 31 '13 at 12:44





      +1 for the here-doc, since the lines variable's only purpose seems to be to feed the while loop.

      – chepner
      May 31 '13 at 12:44













      @chepner: Thx! I added another one, dedicated to You!

      – TrueY
      May 31 '13 at 12:57





      @chepner: Thx! I added another one, dedicated to You!

      – TrueY
      May 31 '13 at 12:57













      There is yet another solution given here: for line in $(echo -e $lines); do ... done

      – dma_k
      Jan 19 '16 at 20:20





      There is yet another solution given here: for line in $(echo -e $lines); do ... done

      – dma_k
      Jan 19 '16 at 20:20













      @dma_k Thanks for your comment! This solution would result 6 lines containing a single word. OP's request was different...

      – TrueY
      Jan 20 '16 at 8:59





      @dma_k Thanks for your comment! This solution would result 6 lines containing a single word. OP's request was different...

      – TrueY
      Jan 20 '16 at 8:59













      upvoted. running echo in a subshell inside here-is, was one of the few solutions that worked in ash

      – Hamy
      Nov 26 '16 at 5:03





      upvoted. running echo in a subshell inside here-is, was one of the few solutions that worked in ash

      – Hamy
      Nov 26 '16 at 5:03











      9














      You are the 742342nd user to ask this bash FAQ. The answer also describes the general case of variables set in subshells created by pipes:




      E4) If I pipe the output of a command into read variable, why
      doesn't the output show up in $variable when the read command finishes?



      This has to do with the parent-child relationship between Unix
      processes. It affects all commands run in pipelines, not just
      simple calls to read. For example, piping a command's output
      into a while loop that repeatedly calls read will result in
      the same behavior.



      Each element of a pipeline, even a builtin or shell function,
      runs in a separate process, a child of the shell running the
      pipeline. A subprocess cannot affect its parent's environment.
      When the read command sets the variable to the input, that
      variable is set only in the subshell, not the parent shell. When
      the subshell exits, the value of the variable is lost.



      Many pipelines that end with read variable can be converted
      into command substitutions, which will capture the output of
      a specified command. The output can then be assigned to a
      variable:



      grep ^gnu /usr/lib/news/active | wc -l | read ngroup


      can be converted into



      ngroup=$(grep ^gnu /usr/lib/news/active | wc -l)


      This does not, unfortunately, work to split the text among
      multiple variables, as read does when given multiple variable
      arguments. If you need to do this, you can either use the
      command substitution above to read the output into a variable
      and chop up the variable using the bash pattern removal
      expansion operators or use some variant of the following
      approach.



      Say /usr/local/bin/ipaddr is the following shell script:



      #! /bin/sh
      host `hostname` | awk '/address/ print $NF'


      Instead of using



      /usr/local/bin/ipaddr | read A B C D


      to break the local machine's IP address into separate octets, use



      OIFS="$IFS"
      IFS=.
      set -- $(/usr/local/bin/ipaddr)
      IFS="$OIFS"
      A="$1" B="$2" C="$3" D="$4"


      Beware, however, that this will change the shell's positional
      parameters. If you need them, you should save them before doing
      this.



      This is the general approach -- in most cases you will not need to
      set $IFS to a different value.



      Some other user-supplied alternatives include:



      read A B C D << HERE
      $(IFS=.; echo $(/usr/local/bin/ipaddr))
      HERE


      and, where process substitution is available,



      read A B C D < <(IFS=.; echo $(/usr/local/bin/ipaddr))






      share|improve this answer




















      • 5





        You forgot the Family Feud problem. Sometimes it’s very hard to find the same combination of words as whoever wrote the answer, so that you’re neither flooded in wrong results, nor filter out said answer.

        – Evi1M4chine
        Jan 29 '16 at 19:20















      9














      You are the 742342nd user to ask this bash FAQ. The answer also describes the general case of variables set in subshells created by pipes:




      E4) If I pipe the output of a command into read variable, why
      doesn't the output show up in $variable when the read command finishes?



      This has to do with the parent-child relationship between Unix
      processes. It affects all commands run in pipelines, not just
      simple calls to read. For example, piping a command's output
      into a while loop that repeatedly calls read will result in
      the same behavior.



      Each element of a pipeline, even a builtin or shell function,
      runs in a separate process, a child of the shell running the
      pipeline. A subprocess cannot affect its parent's environment.
      When the read command sets the variable to the input, that
      variable is set only in the subshell, not the parent shell. When
      the subshell exits, the value of the variable is lost.



      Many pipelines that end with read variable can be converted
      into command substitutions, which will capture the output of
      a specified command. The output can then be assigned to a
      variable:



      grep ^gnu /usr/lib/news/active | wc -l | read ngroup


      can be converted into



      ngroup=$(grep ^gnu /usr/lib/news/active | wc -l)


      This does not, unfortunately, work to split the text among
      multiple variables, as read does when given multiple variable
      arguments. If you need to do this, you can either use the
      command substitution above to read the output into a variable
      and chop up the variable using the bash pattern removal
      expansion operators or use some variant of the following
      approach.



      Say /usr/local/bin/ipaddr is the following shell script:



      #! /bin/sh
      host `hostname` | awk '/address/ print $NF'


      Instead of using



      /usr/local/bin/ipaddr | read A B C D


      to break the local machine's IP address into separate octets, use



      OIFS="$IFS"
      IFS=.
      set -- $(/usr/local/bin/ipaddr)
      IFS="$OIFS"
      A="$1" B="$2" C="$3" D="$4"


      Beware, however, that this will change the shell's positional
      parameters. If you need them, you should save them before doing
      this.



      This is the general approach -- in most cases you will not need to
      set $IFS to a different value.



      Some other user-supplied alternatives include:



      read A B C D << HERE
      $(IFS=.; echo $(/usr/local/bin/ipaddr))
      HERE


      and, where process substitution is available,



      read A B C D < <(IFS=.; echo $(/usr/local/bin/ipaddr))






      share|improve this answer




















      • 5





        You forgot the Family Feud problem. Sometimes it’s very hard to find the same combination of words as whoever wrote the answer, so that you’re neither flooded in wrong results, nor filter out said answer.

        – Evi1M4chine
        Jan 29 '16 at 19:20













      9












      9








      9







      You are the 742342nd user to ask this bash FAQ. The answer also describes the general case of variables set in subshells created by pipes:




      E4) If I pipe the output of a command into read variable, why
      doesn't the output show up in $variable when the read command finishes?



      This has to do with the parent-child relationship between Unix
      processes. It affects all commands run in pipelines, not just
      simple calls to read. For example, piping a command's output
      into a while loop that repeatedly calls read will result in
      the same behavior.



      Each element of a pipeline, even a builtin or shell function,
      runs in a separate process, a child of the shell running the
      pipeline. A subprocess cannot affect its parent's environment.
      When the read command sets the variable to the input, that
      variable is set only in the subshell, not the parent shell. When
      the subshell exits, the value of the variable is lost.



      Many pipelines that end with read variable can be converted
      into command substitutions, which will capture the output of
      a specified command. The output can then be assigned to a
      variable:



      grep ^gnu /usr/lib/news/active | wc -l | read ngroup


      can be converted into



      ngroup=$(grep ^gnu /usr/lib/news/active | wc -l)


      This does not, unfortunately, work to split the text among
      multiple variables, as read does when given multiple variable
      arguments. If you need to do this, you can either use the
      command substitution above to read the output into a variable
      and chop up the variable using the bash pattern removal
      expansion operators or use some variant of the following
      approach.



      Say /usr/local/bin/ipaddr is the following shell script:



      #! /bin/sh
      host `hostname` | awk '/address/ print $NF'


      Instead of using



      /usr/local/bin/ipaddr | read A B C D


      to break the local machine's IP address into separate octets, use



      OIFS="$IFS"
      IFS=.
      set -- $(/usr/local/bin/ipaddr)
      IFS="$OIFS"
      A="$1" B="$2" C="$3" D="$4"


      Beware, however, that this will change the shell's positional
      parameters. If you need them, you should save them before doing
      this.



      This is the general approach -- in most cases you will not need to
      set $IFS to a different value.



      Some other user-supplied alternatives include:



      read A B C D << HERE
      $(IFS=.; echo $(/usr/local/bin/ipaddr))
      HERE


      and, where process substitution is available,



      read A B C D < <(IFS=.; echo $(/usr/local/bin/ipaddr))






      share|improve this answer















      You are the 742342nd user to ask this bash FAQ. The answer also describes the general case of variables set in subshells created by pipes:




      E4) If I pipe the output of a command into read variable, why
      doesn't the output show up in $variable when the read command finishes?



      This has to do with the parent-child relationship between Unix
      processes. It affects all commands run in pipelines, not just
      simple calls to read. For example, piping a command's output
      into a while loop that repeatedly calls read will result in
      the same behavior.



      Each element of a pipeline, even a builtin or shell function,
      runs in a separate process, a child of the shell running the
      pipeline. A subprocess cannot affect its parent's environment.
      When the read command sets the variable to the input, that
      variable is set only in the subshell, not the parent shell. When
      the subshell exits, the value of the variable is lost.



      Many pipelines that end with read variable can be converted
      into command substitutions, which will capture the output of
      a specified command. The output can then be assigned to a
      variable:



      grep ^gnu /usr/lib/news/active | wc -l | read ngroup


      can be converted into



      ngroup=$(grep ^gnu /usr/lib/news/active | wc -l)


      This does not, unfortunately, work to split the text among
      multiple variables, as read does when given multiple variable
      arguments. If you need to do this, you can either use the
      command substitution above to read the output into a variable
      and chop up the variable using the bash pattern removal
      expansion operators or use some variant of the following
      approach.



      Say /usr/local/bin/ipaddr is the following shell script:



      #! /bin/sh
      host `hostname` | awk '/address/ print $NF'


      Instead of using



      /usr/local/bin/ipaddr | read A B C D


      to break the local machine's IP address into separate octets, use



      OIFS="$IFS"
      IFS=.
      set -- $(/usr/local/bin/ipaddr)
      IFS="$OIFS"
      A="$1" B="$2" C="$3" D="$4"


      Beware, however, that this will change the shell's positional
      parameters. If you need them, you should save them before doing
      this.



      This is the general approach -- in most cases you will not need to
      set $IFS to a different value.



      Some other user-supplied alternatives include:



      read A B C D << HERE
      $(IFS=.; echo $(/usr/local/bin/ipaddr))
      HERE


      and, where process substitution is available,



      read A B C D < <(IFS=.; echo $(/usr/local/bin/ipaddr))







      share|improve this answer














      share|improve this answer



      share|improve this answer








      edited Sep 10 '16 at 19:15









      Mark Amery

      66k31261308




      66k31261308










      answered May 31 '13 at 16:09









      JensJens

      51.3k1489136




      51.3k1489136







      • 5





        You forgot the Family Feud problem. Sometimes it’s very hard to find the same combination of words as whoever wrote the answer, so that you’re neither flooded in wrong results, nor filter out said answer.

        – Evi1M4chine
        Jan 29 '16 at 19:20












      • 5





        You forgot the Family Feud problem. Sometimes it’s very hard to find the same combination of words as whoever wrote the answer, so that you’re neither flooded in wrong results, nor filter out said answer.

        – Evi1M4chine
        Jan 29 '16 at 19:20







      5




      5





      You forgot the Family Feud problem. Sometimes it’s very hard to find the same combination of words as whoever wrote the answer, so that you’re neither flooded in wrong results, nor filter out said answer.

      – Evi1M4chine
      Jan 29 '16 at 19:20





      You forgot the Family Feud problem. Sometimes it’s very hard to find the same combination of words as whoever wrote the answer, so that you’re neither flooded in wrong results, nor filter out said answer.

      – Evi1M4chine
      Jan 29 '16 at 19:20











      1














      Hmmm... I would almost swear that this worked for the original Bourne shell, but don't have access to a running copy just now to check.



      There is, however, a very trivial workaround to the problem.



      Change the first line of the script from:



      #!/bin/bash


      to



      #!/bin/ksh


      Et voila! A read at the end of a pipeline works just fine, assuming you have the Korn shell installed.






      share|improve this answer



























        1














        Hmmm... I would almost swear that this worked for the original Bourne shell, but don't have access to a running copy just now to check.



        There is, however, a very trivial workaround to the problem.



        Change the first line of the script from:



        #!/bin/bash


        to



        #!/bin/ksh


        Et voila! A read at the end of a pipeline works just fine, assuming you have the Korn shell installed.






        share|improve this answer

























          1












          1








          1







          Hmmm... I would almost swear that this worked for the original Bourne shell, but don't have access to a running copy just now to check.



          There is, however, a very trivial workaround to the problem.



          Change the first line of the script from:



          #!/bin/bash


          to



          #!/bin/ksh


          Et voila! A read at the end of a pipeline works just fine, assuming you have the Korn shell installed.






          share|improve this answer













          Hmmm... I would almost swear that this worked for the original Bourne shell, but don't have access to a running copy just now to check.



          There is, however, a very trivial workaround to the problem.



          Change the first line of the script from:



          #!/bin/bash


          to



          #!/bin/ksh


          Et voila! A read at the end of a pipeline works just fine, assuming you have the Korn shell installed.







          share|improve this answer












          share|improve this answer



          share|improve this answer










          answered Oct 20 '14 at 18:56









          Jonathan QuistJonathan Quist

          211




          211





















              0














              This is an interesting question and touch a very basic concept in Bourne shell and subshell. Here I provide a solution that is different from the previous solutions by doing some kind of filtering. I will give an example that may be useful in real life. This is a fragment for checking the downloaded files conform to know checksums. The checksum file look like the following (Showing just 3 lines):



              49174 36326 dna_align_feature.txt.gz
              54757 1 dna.txt.gz
              55409 9971 exon_transcript.txt.gz


              The shell script:



              #!/bin/sh

              .....

              failcnt=0 # this variable is only valid in the parent shell
              #variable xx captures all the outputs from the while loop
              xx=$(cat $checkfile | while read -r line; do
              num1=$(echo $line | awk 'print $1')
              num2=$(echo $line | awk 'print $2')
              fname=$(echo $line | awk 'print $3')
              if [ -f "$fname" ]; then
              res=$(sum $fname)
              filegood=$(sum $fname | awk -v na=$num1 -v nb=$num2 -v fn=$fname ' if (na == $1 && nb == $2) print "TRUE"; else print "FALSE"; ')
              if [ "$filegood" = "FALSE" ]; then
              failcnt=$(expr $failcnt + 1) # only in subshell
              echo "$fname BAD $failcnt"
              fi
              fi
              done | tail -1) # I am only interested in the final result
              # you can capture a whole bunch of texts and do further filtering
              failcnt=$xx#* BAD # I am only interested in the number
              # this variable is in the parent shell
              echo failcnt $failcnt
              if [ $failcnt -gt 0 ]; then
              echo $failcnt files failed
              else
              echo download successful
              fi


              The parent and subshell communicate through the echo command. You can pick some easy to parse text for the parent shell. This method does not break your normal way of thinking, just that you have to do some post processing. You can use grep, sed, awk, and more for doing so.






              share|improve this answer



























                0














                This is an interesting question and touch a very basic concept in Bourne shell and subshell. Here I provide a solution that is different from the previous solutions by doing some kind of filtering. I will give an example that may be useful in real life. This is a fragment for checking the downloaded files conform to know checksums. The checksum file look like the following (Showing just 3 lines):



                49174 36326 dna_align_feature.txt.gz
                54757 1 dna.txt.gz
                55409 9971 exon_transcript.txt.gz


                The shell script:



                #!/bin/sh

                .....

                failcnt=0 # this variable is only valid in the parent shell
                #variable xx captures all the outputs from the while loop
                xx=$(cat $checkfile | while read -r line; do
                num1=$(echo $line | awk 'print $1')
                num2=$(echo $line | awk 'print $2')
                fname=$(echo $line | awk 'print $3')
                if [ -f "$fname" ]; then
                res=$(sum $fname)
                filegood=$(sum $fname | awk -v na=$num1 -v nb=$num2 -v fn=$fname ' if (na == $1 && nb == $2) print "TRUE"; else print "FALSE"; ')
                if [ "$filegood" = "FALSE" ]; then
                failcnt=$(expr $failcnt + 1) # only in subshell
                echo "$fname BAD $failcnt"
                fi
                fi
                done | tail -1) # I am only interested in the final result
                # you can capture a whole bunch of texts and do further filtering
                failcnt=$xx#* BAD # I am only interested in the number
                # this variable is in the parent shell
                echo failcnt $failcnt
                if [ $failcnt -gt 0 ]; then
                echo $failcnt files failed
                else
                echo download successful
                fi


                The parent and subshell communicate through the echo command. You can pick some easy to parse text for the parent shell. This method does not break your normal way of thinking, just that you have to do some post processing. You can use grep, sed, awk, and more for doing so.






                share|improve this answer

























                  0












                  0








                  0







                  This is an interesting question and touch a very basic concept in Bourne shell and subshell. Here I provide a solution that is different from the previous solutions by doing some kind of filtering. I will give an example that may be useful in real life. This is a fragment for checking the downloaded files conform to know checksums. The checksum file look like the following (Showing just 3 lines):



                  49174 36326 dna_align_feature.txt.gz
                  54757 1 dna.txt.gz
                  55409 9971 exon_transcript.txt.gz


                  The shell script:



                  #!/bin/sh

                  .....

                  failcnt=0 # this variable is only valid in the parent shell
                  #variable xx captures all the outputs from the while loop
                  xx=$(cat $checkfile | while read -r line; do
                  num1=$(echo $line | awk 'print $1')
                  num2=$(echo $line | awk 'print $2')
                  fname=$(echo $line | awk 'print $3')
                  if [ -f "$fname" ]; then
                  res=$(sum $fname)
                  filegood=$(sum $fname | awk -v na=$num1 -v nb=$num2 -v fn=$fname ' if (na == $1 && nb == $2) print "TRUE"; else print "FALSE"; ')
                  if [ "$filegood" = "FALSE" ]; then
                  failcnt=$(expr $failcnt + 1) # only in subshell
                  echo "$fname BAD $failcnt"
                  fi
                  fi
                  done | tail -1) # I am only interested in the final result
                  # you can capture a whole bunch of texts and do further filtering
                  failcnt=$xx#* BAD # I am only interested in the number
                  # this variable is in the parent shell
                  echo failcnt $failcnt
                  if [ $failcnt -gt 0 ]; then
                  echo $failcnt files failed
                  else
                  echo download successful
                  fi


                  The parent and subshell communicate through the echo command. You can pick some easy to parse text for the parent shell. This method does not break your normal way of thinking, just that you have to do some post processing. You can use grep, sed, awk, and more for doing so.






                  share|improve this answer













                  This is an interesting question and touch a very basic concept in Bourne shell and subshell. Here I provide a solution that is different from the previous solutions by doing some kind of filtering. I will give an example that may be useful in real life. This is a fragment for checking the downloaded files conform to know checksums. The checksum file look like the following (Showing just 3 lines):



                  49174 36326 dna_align_feature.txt.gz
                  54757 1 dna.txt.gz
                  55409 9971 exon_transcript.txt.gz


                  The shell script:



                  #!/bin/sh

                  .....

                  failcnt=0 # this variable is only valid in the parent shell
                  #variable xx captures all the outputs from the while loop
                  xx=$(cat $checkfile | while read -r line; do
                  num1=$(echo $line | awk 'print $1')
                  num2=$(echo $line | awk 'print $2')
                  fname=$(echo $line | awk 'print $3')
                  if [ -f "$fname" ]; then
                  res=$(sum $fname)
                  filegood=$(sum $fname | awk -v na=$num1 -v nb=$num2 -v fn=$fname ' if (na == $1 && nb == $2) print "TRUE"; else print "FALSE"; ')
                  if [ "$filegood" = "FALSE" ]; then
                  failcnt=$(expr $failcnt + 1) # only in subshell
                  echo "$fname BAD $failcnt"
                  fi
                  fi
                  done | tail -1) # I am only interested in the final result
                  # you can capture a whole bunch of texts and do further filtering
                  failcnt=$xx#* BAD # I am only interested in the number
                  # this variable is in the parent shell
                  echo failcnt $failcnt
                  if [ $failcnt -gt 0 ]; then
                  echo $failcnt files failed
                  else
                  echo download successful
                  fi


                  The parent and subshell communicate through the echo command. You can pick some easy to parse text for the parent shell. This method does not break your normal way of thinking, just that you have to do some post processing. You can use grep, sed, awk, and more for doing so.







                  share|improve this answer












                  share|improve this answer



                  share|improve this answer










                  answered Mar 7 '18 at 4:02









                  Kemin ZhouKemin Zhou

                  2,3741833




                  2,3741833





















                      -1














                      How about a very simple method



                       +call your while loop in a function 
                      - set your value inside (nonsense, but shows the example)
                      - return your value inside
                      +capture your value outside
                      +set outside
                      +display outside


                      #!/bin/bash
                      # set -e
                      # set -u
                      # No idea why you need this, not using here

                      foo=0
                      bar="hello"

                      if [[ "$bar" == "hello" ]]
                      then
                      foo=1
                      echo "Setting $foo to $foo"
                      fi

                      echo "Variable $foo after if statement: $foo"

                      lines="first linensecond linenthird line"

                      function my_while_loop


                      echo -e $lines

                      my_while_loop; foo="$?"

                      echo "Variable $foo after while loop: $foo"


                      Output:
                      Setting $foo 1
                      Variable $foo after if statement: 1
                      Value of $foo in while loop body: 1
                      Variable $foo after while loop: 2

                      bash --version

                      GNU bash, version 3.2.51(1)-release (x86_64-apple-darwin13)
                      Copyright (C) 2007 Free Software Foundation, Inc.





                      share|improve this answer




















                      • 6





                        Maybe there's a decent answer under the surface here, but you've butchered the formatting to the point that it's unpleasant to try and read.

                        – Mark Amery
                        Sep 10 '16 at 19:42











                      • You mean the original code is pleasant to read? (I've just followed :p)

                        – Marcin
                        Aug 11 '17 at 14:20















                      -1














                      How about a very simple method



                       +call your while loop in a function 
                      - set your value inside (nonsense, but shows the example)
                      - return your value inside
                      +capture your value outside
                      +set outside
                      +display outside


                      #!/bin/bash
                      # set -e
                      # set -u
                      # No idea why you need this, not using here

                      foo=0
                      bar="hello"

                      if [[ "$bar" == "hello" ]]
                      then
                      foo=1
                      echo "Setting $foo to $foo"
                      fi

                      echo "Variable $foo after if statement: $foo"

                      lines="first linensecond linenthird line"

                      function my_while_loop


                      echo -e $lines

                      my_while_loop; foo="$?"

                      echo "Variable $foo after while loop: $foo"


                      Output:
                      Setting $foo 1
                      Variable $foo after if statement: 1
                      Value of $foo in while loop body: 1
                      Variable $foo after while loop: 2

                      bash --version

                      GNU bash, version 3.2.51(1)-release (x86_64-apple-darwin13)
                      Copyright (C) 2007 Free Software Foundation, Inc.





                      share|improve this answer




















                      • 6





                        Maybe there's a decent answer under the surface here, but you've butchered the formatting to the point that it's unpleasant to try and read.

                        – Mark Amery
                        Sep 10 '16 at 19:42











                      • You mean the original code is pleasant to read? (I've just followed :p)

                        – Marcin
                        Aug 11 '17 at 14:20













                      -1












                      -1








                      -1







                      How about a very simple method



                       +call your while loop in a function 
                      - set your value inside (nonsense, but shows the example)
                      - return your value inside
                      +capture your value outside
                      +set outside
                      +display outside


                      #!/bin/bash
                      # set -e
                      # set -u
                      # No idea why you need this, not using here

                      foo=0
                      bar="hello"

                      if [[ "$bar" == "hello" ]]
                      then
                      foo=1
                      echo "Setting $foo to $foo"
                      fi

                      echo "Variable $foo after if statement: $foo"

                      lines="first linensecond linenthird line"

                      function my_while_loop


                      echo -e $lines

                      my_while_loop; foo="$?"

                      echo "Variable $foo after while loop: $foo"


                      Output:
                      Setting $foo 1
                      Variable $foo after if statement: 1
                      Value of $foo in while loop body: 1
                      Variable $foo after while loop: 2

                      bash --version

                      GNU bash, version 3.2.51(1)-release (x86_64-apple-darwin13)
                      Copyright (C) 2007 Free Software Foundation, Inc.





                      share|improve this answer















                      How about a very simple method



                       +call your while loop in a function 
                      - set your value inside (nonsense, but shows the example)
                      - return your value inside
                      +capture your value outside
                      +set outside
                      +display outside


                      #!/bin/bash
                      # set -e
                      # set -u
                      # No idea why you need this, not using here

                      foo=0
                      bar="hello"

                      if [[ "$bar" == "hello" ]]
                      then
                      foo=1
                      echo "Setting $foo to $foo"
                      fi

                      echo "Variable $foo after if statement: $foo"

                      lines="first linensecond linenthird line"

                      function my_while_loop


                      echo -e $lines

                      my_while_loop; foo="$?"

                      echo "Variable $foo after while loop: $foo"


                      Output:
                      Setting $foo 1
                      Variable $foo after if statement: 1
                      Value of $foo in while loop body: 1
                      Variable $foo after while loop: 2

                      bash --version

                      GNU bash, version 3.2.51(1)-release (x86_64-apple-darwin13)
                      Copyright (C) 2007 Free Software Foundation, Inc.






                      share|improve this answer














                      share|improve this answer



                      share|improve this answer








                      edited Aug 30 '14 at 3:54

























                      answered Aug 30 '14 at 3:40









                      MarcinMarcin

                      9913




                      9913







                      • 6





                        Maybe there's a decent answer under the surface here, but you've butchered the formatting to the point that it's unpleasant to try and read.

                        – Mark Amery
                        Sep 10 '16 at 19:42











                      • You mean the original code is pleasant to read? (I've just followed :p)

                        – Marcin
                        Aug 11 '17 at 14:20












                      • 6





                        Maybe there's a decent answer under the surface here, but you've butchered the formatting to the point that it's unpleasant to try and read.

                        – Mark Amery
                        Sep 10 '16 at 19:42











                      • You mean the original code is pleasant to read? (I've just followed :p)

                        – Marcin
                        Aug 11 '17 at 14:20







                      6




                      6





                      Maybe there's a decent answer under the surface here, but you've butchered the formatting to the point that it's unpleasant to try and read.

                      – Mark Amery
                      Sep 10 '16 at 19:42





                      Maybe there's a decent answer under the surface here, but you've butchered the formatting to the point that it's unpleasant to try and read.

                      – Mark Amery
                      Sep 10 '16 at 19:42













                      You mean the original code is pleasant to read? (I've just followed :p)

                      – Marcin
                      Aug 11 '17 at 14:20





                      You mean the original code is pleasant to read? (I've just followed :p)

                      – Marcin
                      Aug 11 '17 at 14:20

















                      draft saved

                      draft discarded
















































                      Thanks for contributing an answer to Stack Overflow!


                      • Please be sure to answer the question. Provide details and share your research!

                      But avoid


                      • Asking for help, clarification, or responding to other answers.

                      • Making statements based on opinion; back them up with references or personal experience.

                      To learn more, see our tips on writing great answers.




                      draft saved


                      draft discarded














                      StackExchange.ready(
                      function ()
                      StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f16854280%2fa-variable-modified-inside-a-while-loop-is-not-remembered%23new-answer', 'question_page');

                      );

                      Post as a guest















                      Required, but never shown





















































                      Required, but never shown














                      Required, but never shown












                      Required, but never shown







                      Required, but never shown

































                      Required, but never shown














                      Required, but never shown












                      Required, but never shown







                      Required, but never shown







                      Popular posts from this blog

                      Kamusi Yaliyomo Aina za kamusi | Muundo wa kamusi | Faida za kamusi | Dhima ya picha katika kamusi | Marejeo | Tazama pia | Viungo vya nje | UrambazajiKuhusu kamusiGo-SwahiliWiki-KamusiKamusi ya Kiswahili na Kiingerezakuihariri na kuongeza habari

                      Swift 4 - func physicsWorld not invoked on collision? The Next CEO of Stack OverflowHow to call Objective-C code from Swift#ifdef replacement in the Swift language@selector() in Swift?#pragma mark in Swift?Swift for loop: for index, element in array?dispatch_after - GCD in Swift?Swift Beta performance: sorting arraysSplit a String into an array in Swift?The use of Swift 3 @objc inference in Swift 4 mode is deprecated?How to optimize UITableViewCell, because my UITableView lags

                      Access current req object everywhere in Node.js ExpressWhy are global variables considered bad practice? (node.js)Using req & res across functionsHow do I get the path to the current script with Node.js?What is Node.js' Connect, Express and “middleware”?Node.js w/ express error handling in callbackHow to access the GET parameters after “?” in Express?Modify Node.js req object parametersAccess “app” variable inside of ExpressJS/ConnectJS middleware?Node.js Express app - request objectAngular Http Module considered middleware?Session variables in ExpressJSAdd properties to the req object in expressjs with Typescript