首页 > 系统相关 >while读取文件 Shell中while循环的陷阱, 变量实效, 无法赋值变量

while读取文件 Shell中while循环的陷阱, 变量实效, 无法赋值变量

时间:2023-08-14 23:45:00浏览次数:48  
标签:do head Shell 变量 while done 写法 读取

 

在写while循环的时候,发现了一个问题,在while循环内部对变量赋值、定义变量、数组定义等等环境,在循环外面失效。

一个简单的测试脚本如下:

#!/bin/bash
echo "abc xyz" | while read line
do
    new_var=$line
done
echo new_var is null: $new_var?

 

  1. 执行结果证明,$new_var的结果是空值。在google上查了查,才发现问题出在管道上。加上自己的总结,特分享两个使用while循环时的陷阱。

先看看下面的内容。

while循环的写法有好几种,它的语法结构为

while test_cmd_list; do cmd_list; done

但更经常地,while循环更多地用于读取标准输入的内容来实现循环。有以下几种写法:

写法一:使用管道传递内容,这是用的最多、但却最烂的写法

echo "abc xyz" | while read line   
 
do 
 
    ...
 
done

 

写法二:

  1. while read line
     
    do
     
        ...
     
    done <<< "abc xyz"

     

写法三:从文件中读取内容

  1. while read line
     
    do
     
        ...
     
    done </path/filename

     

方法四:采用进程替换

  1. while read var
     
    do
     
        ...
     
    done < <(cmd_list)

     

方法五:改变标准输入

  1. exec <filename
     
    while read var
     
    do
     
        ...
     
    done

     

尽管写法有多种,但它们并不等价。

 

陷阱一:

方法一中使用的是管道符号,这使得while语句在子shell中执行,这意味着while语句内部设置的变量、数组、函数等在循环外部都不再生效。这正是文章开头所说的陷阱。更简单的:echo haha | a=5,在命令执行结束后,变量a的值也不再是5。其余4种写法,while语句都不在子shell中执行,因此都不会出现文章开头所说的问题。

例如,使用写法二的here string代替写法一:

  1. #!/bin/bash
    while read line
    do
        new_var=$line
    done <<< "abc xyz"
    echo new_var is null: $new_var?

     

或者使用写法四的进程替换:

  1. #!/bin/bash
    while read line
    do
        new_var=$line
    done < <(echo "abc xyz")
    echo new_var is null: $new_var?

     

陷阱二:

关于这几种while循环的写法,还有一点要注意:写法一和写法四传递数据的源都是一个单独的进程,它们传递的数据一被while循环读取,所有数据就丢弃了,而以实体文件作为重定向传递的数据,while读取了之后并不会丢弃。更标准一些的说法是,当标准输入是非实体文件时(如管道传递的、独立进程产生的)只供一次读取;当标准输入是直接重定向实体文件时,可供多次读取,但只要某一次读取了该文件的全部内容就无法再提供读取。

举个例子,老师让我们听写10个单词,而我记忆力比较烂,他念完10个单词时我可能只写出了3个,剩余的7个因为记不住就没法再写出来。但如果我有小抄,我就可以慢悠悠的一个一个写,写了一个还可以等一段时间再写第二个,但当我写完10个之后,小抄这种东西就应该销毁掉。

回到IO重定向上,无论什么数据资源,只要被读取完毕或者主动丢弃,那么该资源就不可再得。①对于独立进程传递的数据(管道左侧进程产生的数据、进程替换产生的数据),它们都是”虚拟”数据,要不被一次读取完毕,要不读一部分剩余的丢弃,这是真正的一次性资源。②而实体文件重定向传递的数据,只要不是一次性被全部读取,它就是可再得资源,直到该文件数据全部读取结束,这是”伪”一次性资源。其实①是进程间通信时数据传递的现象,只不过这个问题容易被人忽略。

大多数时候,独立进程传递的数据和文件直接传递的数据并没有什么区别,但有些命令可以标记当前读取到哪个位置,使得下次该命令的读取动作可以从标记位置处恢复并继续读取,特别是这些命令用在循环中时。据我到目前的总结,这样的命令有”head -n N”和”grep -m”,经测试,tail并没有位置标记的功能,因为tail读取的是后几行,所以它必然要读取到最后一行并计算要输出的行,所以tail的性能比head要差。

说了这么多,现在终于开始验证。下面的循环中,本该head每次读取2行,但实际执行结果中总共就只读取了一次2行。

  1. [root@xuexi ~]# i=0
    [root@xuexi ~]# cat /etc/fstab | while head -n 2 ; [[ "$i" -le 3 ]];do echo $i;let ++i;done     
     
    #
    0
    1
    2
    3

     

使用进程替换的结果是一样的。

  1. [root@xuexi ~]# i=0
    [root@xuexi ~]# while head -n 2; [[ "$i" -le 3 ]];do echo $i;let ++i;done < <(cat /etc/fstab)
     
    #
    0
    1
    2
    3

     

但如果是直接将实体文件进行重定向传递给head,则结果和上面的不一样。

  1. [root@xuexi ~]# i=0;while head -n 2 ; [[ "$i" -le 3 ]];do echo $i;let ++i;done < /etc/fstab
     
    #
    0
    # /etc/fstab
    # Created by anaconda on Thu May 11 04:17:44 2017
    1
    #
    # Accessible filesystems, by reference, are maintained under '/dev/disk'
    2
    # See man pages fstab(5), findfs(8), mount(8) and/or blkid(8) for more info
    #
    3
    UUID=b2a70faf-aea4-4d8e-8be8-c7109ac9c8b8 /                       xfs     defaults        0 0
    UUID=367d6a77-033b-4037-bbcb-416705ead095 /boot                   xfs     defaults        0 0

     

可以看到结果中每次读取两行并echo一次”$i”,而且每次读取的两行是不同的,后一次读取的两行是从前一次读取结束的地方开始的,这是因为head有”读取到指定行数后做上位置标记”的功能。

要确定命令、工具是否具有做位置标记的能力,只需像下面例子一样做个简单的测试。以head和sed为例,即使sed的”q”命令能让sed匹配到内容就退出,但却不做位置标记,而且数据资源使用一次就丢弃。

  1. [root@xuexi ~]# (head -n 2;head -n 2) </etc/fstab 
     
    #
    # /etc/fstab
    # Created by anaconda on Thu May 11 04:17:44 2017

     

其实在实际应用过程中,这根本就不是个问题,因为搜索和处理文本数据的工具虽然不少,但绝大多数都是用一次文本就”丢”一次,几乎不可能因此而产生问题。之所以说这么多废话,主要是想说上面的5种while写法中,使用最广泛的写法一虽然最简单、方便,但其实是最烂的一种。

 

转载地址:http://justcode.ikeepstudying.com/2018/02/shell%EF%BC%9A-shell%E4%B8%ADwhile%E5%BE%AA%E7%8E%AF%E7%9A%84%E9%99%B7%E9%98%B1-%E5%8F%98%E9%87%8F%E5%AE%9E%E6%95%88-%E6%97%A0%E6%B3%95%E8%B5%8B%E5%80%BC%E5%8F%98%E9%87%8F/

 

https://blog.csdn.net/tanghuan0827/article/details/110962339

标签:do,head,Shell,变量,while,done,写法,读取
From: https://www.cnblogs.com/pengmn/p/17630089.html

相关文章

  • () {}用法,Linux shell脚本编程时bad substitution解决办法
     首先,我们要理解badsubstitution的字面意思,它的字面意思是“替换错误”的意思。这种错误的原因呢,通常是我们编写脚本时“{}”和“()”错误使用导致的。比如应该用“()”时,我们用成了“{}”;应该用“()”时,用成了“{}”。 那么如何区分什么时候用什么类型的括号呢?${}中,大括号里......
  • shell命令概述 Shell作用:命令解释器 介于操作系统内核与用户之间,负责解释命令行 获得
    shell命令概述Shell作用:命令解释器介于操作系统内核与用户之间,负责解释命令行获得命令帮助内部命令help命令的“--help”选项使用man命令阅读手册页命令行编辑的几个辅助操作Tab键:自动补齐反斜杠“\”:强制换行快捷键Ctrl+U:清空至行首快捷键Ctrl+K:清空至行尾快捷键Ctr......
  • 第7章 用户输入和while循环
    7.1函数input()的工作原理1.input()函数让程序暂停等待用户的输入2.输入完成需要回车让程序知道你输入完成7.1.1编写清晰的程序1.x+=y相等于x=x+y7.1.2使用int()来获取数值输入1.我们可以使用int()函数把str转为int7.1.3求模运算符1.求模符号(%),是把两个数的余数......
  • xshell批量新建会话
    1,xshell的会话形式就是文件夹和文件,我们只需要修改文件名称即可,但是一个个弄很麻烦,可以利用这个程序来批量新建(程序很烂,但能用)基于python编写,文件处理-->文件下载提取码:n4wd下载后解压文件,后得到三个文件,demos为xhsll模板文件(不清楚文件内容不要修改),ip_host为ip文件第一步......
  • Linux命令系列(3) —— 单会话多shell命令:screen
    目录一.命令简介二.安装三.常用命令1.查看shell2.创建shell3.离开shell4.进入shell5.销毁shell一.命令简介  在使用Linux的时候,有些情况下只能使用单个会话,但是又有多shell的需求;又有一种情况,需要执行一个在会话结束以后依旧需要执行的进程(在Linux系统中,用户通过sh......
  • vite中导入全局样式变量(less、sass)
    1、先新建vars.less文件,定义基础样式变量2、在vite.config.ts下添加配置import{defineConfig}from'vite'importvuefrom'@vitejs/plugin-vue'importpathfrom"path";//https://vitejs.dev/config/exportdefaultdefineConfig({plugins:[vue(......
  • Java入门学习——变量里的数据在计算机中的存储原理
    变量里的数据在计算机中的存储原理一、二进制只有0、1,按照逢2进1的方式表示数据:十进制转二进制的算法除二取余法。  结果:6的二进制是110  结果:13的二进制是1101二、计算机中表示数据的最小单元计算机中表示数据的最小单位:一个字节(byte,简称B,是使用8个二进......
  • 给id拼接变量,给nth-child()拼接变量
    <divclass="daic"><pclass="reward"></p><pclass="reward"></p><pclass="reward"></p><pclass="reward"></p><pclass="reward&......
  • Go 语言变量作用域
    局部变量在函数体内声明的变量称之为局部变量,它们的作用域只在函数体内packagemainimport"fmt"funcmain(){/*声明局部变量*/vara,b,cint/*初始化参数*/a=10b=20c=a+bfmt.Printf("结果:a=%d,b=%dandc=%d\n",a,b,c)}......
  • Linux之shell脚本的循环
    一、循环语句1.1forhelpfor帮助文档foriinabc;doechohello;done[root@localhostdata]#foriinabc;doechohello;donehellohellohelloforiinabc;doecho$i;done[root@localhostdata]#foriinabc;doecho$i;doneabc[root@localhostd......