从管道读取的 while read 循环后重置变量

发布于 2024-12-03 02:38:46 字数 1389 浏览 0 评论 0原文

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
    else
        filcount=$[$filcount+1]
        bzip $loc
    fi
    if [ "$scan" == "1" ]; then bzipint $loc
    fi
    echo $filcount    #Correct counting
    echo $zipcount    #Correct counting
    echo $scacount    #Correct counting
    echo $valid       #Equal to 1
done

echo $filcount    #Reset to 0
echo $zipcount    #Reset to 0
echo $scacount    #Reset to 0
echo $valid       #Still equal to 1
}

我正在编写一个 bash shell 脚本,以使用 bzip2 来压缩目录中的所有 .bsp 文件。在此脚本中,我有几个用于计算总数的变量(文件、成功的 zip、成功的完整性扫描),但是我似乎遇到了问题。

find $loc -name "*.bsp" 耗尽文件以提供 while readwhile read 退出时,它会清零 < code>$filcount、$zipcount$scacount (所有这些都在 initiate () 内更改(增加),< code>bzip () (即在 initiate () 期间调用)或 bzipint () (也在 initiate () 中调用),

以测试它是否需要执行 。对于在 initiate () 内部更改变量或从其访问的其他函数,我使用了 echo $valid,它是在 initiate () 外部定义的(喜欢$filcount$zipcount 等),但不会从 initiate()initiate() 内的另一个函数更改 有趣的是

$valid 不会像 initiate 中的其他变量一样重置为 0。

谁能告诉我为什么当 read 退出时我的变量会神奇地重置?

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
    else
        filcount=$[$filcount+1]
        bzip $loc
    fi
    if [ "$scan" == "1" ]; then bzipint $loc
    fi
    echo $filcount    #Correct counting
    echo $zipcount    #Correct counting
    echo $scacount    #Correct counting
    echo $valid       #Equal to 1
done

echo $filcount    #Reset to 0
echo $zipcount    #Reset to 0
echo $scacount    #Reset to 0
echo $valid       #Still equal to 1
}

I'm writing a bash shell script to use bzip2 to zip up all .bsp files inside a directory. In this script I have several variables for counting totals (files, successful zips, successful integrity scans), however I seem to have run into a problem.

When find $loc -name "*.bsp" runs out of files to give the while read and while read exits, it zeros out $filcount, $zipcount and $scacount (all of which are changed (increased) inside initiate (), bzip () (which is called during initiate ()) or bzipint () (which is also called in initiate ()).

In order to test if it's something to do with variables changing inside initiate () or other functions accessed from it, I used echo $valid, which is defined outside of initiate () (like $filcount, $zipcount, etc.), but is not changed from another function inside initiate () or inside initiate () itself.

Interestingly enough, $valid does not get reset to 0 like the other variables inside initiate.

Can anyone tell me why my variables magically get reset when while read exits?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

烟酒忠诚 2024-12-10 02:38:46

如果你使用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")

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")
鸠魁 2024-12-10 02:38:46

我昨天遇到了这个问题。

问题是你正在做 find $loc -name "*.bsp" |阅读时。因为这涉及管道,所以 while read 循环实际上无法与脚本的其余部分在同一个 bash 进程中运行; bash 必须生成一个子进程,以便它可以将 find 的标准输出连接到 while 循环的标准输入。

这一切都非常聪明,但这意味着循环中设置的任何变量在循环之后都看不到,这完全违背了我编写的 while 循环的全部目的。

您可以尝试在不使用管道的情况下向循环提供输入,或者在不使用变量的情况下从循环获取输出。我最终得到了一个可怕的令人厌恶的结果,涉及写入临时文件并将整个循环包装在 $(...) 中,如下所示:

var="$(producer | while read line; do
    ...
    echo "${something}"
done)"

这让我 var 设置为所有已回显的内容从循环中。我可能搞乱了那个例子的语法;我目前手边没有我写的代码。

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.

夏了南城 2024-12-10 02:38:46

总结选项,以便在类似 POSIX 的 shell 中在管道[概念上等效]的末尾使用read

回顾一下:< code>bash 默认并且在严格符合 POSIX 标准的 shell 中始终管道中的所有命令都在子 shell 中运行 >,所以变量创建或修改对当前 shell不可见(管道结束后将不存在)。

以下涵盖bashkshzshsh([大部分]仅限 POSIX 功能的 shell,例如 dash),并展示避免创建子 shell 的方法,以保留通过 read 创建/修改的变量强>。

如果没有给出最低版本号,则假设即使是“相当旧”的版本也支持它(相关功能已经存在很长时间了,但我不知道具体是什么时候引入的。

请注意,作为以下解决方案的[POSIX 兼容]替代方案,您始终可以在[临时]文件中捕获命令的输出,然后将其提供给read< /code> as< file,这也避免了子 shell。


kshzsh:根本不需要解决方法/配置更改:

read 内置默认当用作管道中的last命令时,在当前 shell中运行。

看起来,kshzsh 默认最后中运行任何命令> 当前 shell 中管道的阶段,如在 ksh 93u+zsh 5.0.5 中观察到的。
如果您具体知道该功能是在哪个版本中引入的,请告诉我。

#!/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+:使用 lastpipe shell 选项

在 bash 4.2 或更高版本中,打开 shell 选项 lastpipe 会导致最后一个管道段在当前 shell 中运行,允许读取创建当前 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/'

bashkshzsh:使用 进程替换

宽泛地说,进程替换是一种使命令的输出充当临时文件的方法。

out=
while read -r var; do
  out+="$var/"
done < <(printf '%s\n' one two three) # <(...) is the process substitution

echo "$out" # -> 'one/two/three'

bashkshzsh:使用 这里字符串 带有 命令替换

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'

请注意,需要双引号命令替换以保护其输出 shell 扩展


符合 POSIX 标准的解决方案 (sh):使用 here-document命令替换

#!/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'

请注意,默认情况下,您需要放置结束分隔符 - EOF,在本例中 - 位于行的最开头,并且后面不能有任何字符。

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, as 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 <<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.

无敌元气妹 2024-12-10 02:38:46

我来到这里是因为我经常使用这种范例:

find ${yourPath} -type f -print0 | while IFS= read -rd '' eachFile;do 
    echo "${eachFile}" #show all the files, add more code to do real work
done

并发现 while 循环中的变量位于子 shell 中,因此这不会产生任何结果:

find ${yourPath} -type f -print0 | while IFS= read -rd '' eachFile;do 
    echo "${eachFile}" #show all the files, add more code to do real work
done
echo "${eachFile}" #we cannot get the last value of eachFile!

这仍然不起作用:

shopt -s lastpipe 
find ${yourPath} -type f -print0 | while IFS= read -rd '' eachFile;do 
    echo "${eachFile}" #show all the files, add more code to do real work
done
echo "${eachFile}" #we cannot get the last value of eachFile; even with lastPipe!

这就是有效的:

shopt -s lastpipe 
find ${yourPath} -type f -print0 | while IFS= read -rd '' eachFile;do 
    echo "${eachFile}" #show all the files, add more code to do real work
    latestFile="${eachFile}"
done
echo "${eachFile}" #we cannot get the last value of eachFile; even with lastPipe!
echo "${latestFile}" #but we can get this!

Per Mr Duffy: read 的最后一个实例,决定没有更多内容并返回 false,因此循环退出,清除变量。这是故意的行为,因为它可以让您知道流中最后一个 NUL 之后是否有额外的内容:如果在 read 返回 false 后 $eachFile 中有任何内容,则告诉您该内容不是以 NUL 结尾的

I landed here because I frequently use this paradigm:

find ${yourPath} -type f -print0 | while IFS= read -rd '' eachFile;do 
    echo "${eachFile}" #show all the files, add more code to do real work
done

and found out the hard way that variables within the while loop were in a subshell, so this would yield nothing:

find ${yourPath} -type f -print0 | while IFS= read -rd '' eachFile;do 
    echo "${eachFile}" #show all the files, add more code to do real work
done
echo "${eachFile}" #we cannot get the last value of eachFile!

This still doesn't work:

shopt -s lastpipe 
find ${yourPath} -type f -print0 | while IFS= read -rd '' eachFile;do 
    echo "${eachFile}" #show all the files, add more code to do real work
done
echo "${eachFile}" #we cannot get the last value of eachFile; even with lastPipe!

This is what works:

shopt -s lastpipe 
find ${yourPath} -type f -print0 | while IFS= read -rd '' eachFile;do 
    echo "${eachFile}" #show all the files, add more code to do real work
    latestFile="${eachFile}"
done
echo "${eachFile}" #we cannot get the last value of eachFile; even with lastPipe!
echo "${latestFile}" #but we can get this!

Per Mr Duffy: The last instance of read, the one deciding that there's no more content and returning false so the loop exits, clears the variable. That's deliberate and intentional behavior, because it lets you tell if there's extra content after the last NUL in the stream: if there's anything in $eachFile after read returned false, that tells you that the content wasn't NUL-terminated

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文