Variables getting reset after the while read loop that reads from a pipeline

前端 未结 3 1442
温柔的废话
温柔的废话 2020-11-27 07:45
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
         


        
3条回答
  •  遥遥无期
    2020-11-27 08:09

    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 option

    In 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 substitution

    Loosely 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 substitution

    out=
    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.


    POSIX-compliant solution (sh): use a here-document with a command substitution

    #!/bin/sh
    
    out=
    while read -r var; do
      out="$out$var/"
    done < '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.

提交回复
热议问题