Contents

Linux中read命令简介

1. 简介

当我们创建 Bash 脚本时,接受用户输入会很方便。在本教程中,我们将了解如何使用*read *命令执行此操作。

Bash read命令是一个强大的内置实用程序,用于在 Linux 下对字符串进行分

由于它是一个内置命令,只要我们有 Bash 可用,就不需要额外的设置步骤

2.基本语法

让我们探索包括所有可选参数的基本语法:

$ read [options] [name...]

选项参数影响read命令与输入交互的方式。我们将在接下来的部分中探讨这些。

name参数指定将拆分操作产生的实际单词存储在哪些变量中。

2.1. 默认行为

** read的无参数调用从标准输入流中获取一行并将其分配给REPLY内置变量**:

$ read
blogdemo is a cool tech site # what we type
$ echo $REPLY
blogdemo is a cool tech site

现在让我们指定一些变量来存储结果:

$ read input1 input2 input3
blogdemo is a cool tech site # what we type
$ echo "[$input1] [$input2] [$input3]"
[blogdemo] [is] [a cool tech site]

如果变量的数量少于获得的单词,则所有剩余的单词及其分隔符都被塞入最后一个变量中。

默认情况下,read命令将输入拆分为单词,将spacetab换行字符视为单词分隔符

我们可以使用特殊的 \ 字符在多行上传递输入:

$ read input1 input2 input3
blogdemo \ # what
is a cool \ # we
tech site   # type
$ echo "[$input1] [$input2] [$input3]"
[blogdemo] [is] [a cool tech site]

让我们仔细看看这个例子。除了第 4 行,我们使用 \ 来转义每一行(2 和 3)上的换行字符。

换行字符还用作默认输入行分隔符。我们稍后会看到如何改变这种行为。

2.2. 内部字段分隔符

内部字段分隔符(IFS)确定给定行中的字边界。我们可以修改它以适应我们的需要并处理自定义行:

$ {
      IFS=";"
      read input1 input2 input3
      echo "[$input1] [$input2] [$input3]"
  }
blogdemo;;is;a;cool;tech;site # what we type
[blogdemo] [] [is;a;cool;tech;site]

请注意,第二个单词是空的,因为两个分号字符之间没有任何内容。

让我们再次运行相同的函数,但现在让我们使用空格作为内部字段分隔符

$ {
      IFS=" "
      read input1 input2 input3
      echo "[$input1] [$input2] [$input3]"
  }
blogdemo is  a cool tech site # what we type
[blogdemo] [is] [a cool tech site]

这次输出发生了变化,因为分词的行为与空格字符不同。空格字符序列被视为分隔符。

2.3. 返回代码

成功时返回码为 0。如果发生错误或遇到EOF ,则返回码大于 0:

$ {
      read input1-abc
      echo "return code [$?]"
  }
bash: read: `input1-abc': not a valid identifier
return code [1]

在这个小片段中,我们尝试传递一个无效的变量名,然后我们打印退出状态。

2.4. 添加基本选项

让我们看一下我们可以使用的一些最基本的选项:

  • -a 数组:将单词拆分操作的结果存储在数组中而不是单独的变量中
  • -e:使用 Bash 内置的Readline 库读取输入行
  • *-s:*不将输入行回显到标准输出流
  • -p prompt : 在从标准输入流中请求输入之前打印提示文本,不带换行字符
  • -i text:在标准输出流上打印文本作为默认输入(只能与*-e*结合使用)

现在让我们使用*-s-i*选项实现一个带有隐藏字符的简单密码提示:

$ {
      prompt="You shall not pass:"
      read -p "$prompt" -s input
      echo -e "\n input word [$input]"
  }
You shall not pass: # invisible input here
input word [baledung is a cool site]

在某些情况下,单词的数量因输入而异,因此我们可以利用基于数组的变量:

$ {
      declare -a input_array
      text="blogdemo is a cool tech site"
      read -e -i "$text" -a input_array 
      for input in ${input_array[@]} 
          do
              echo -n "[$input] "
          done
  }
blogdemo is a cool tech site # default input here
[blogdemo] [is] [a] [cool] [tech] [site] 

-i 选项允许我们 **指定默认输入行。借助Readline内置库,**我们还可以使用 -e 选项轻松编辑输入。

3. 高级语法

现在我们已经看到了read的实际应用,让我们来看看一些更高级的选项

  • -d delim:指定输入行的分隔符而不是使用换行字符
  • -u *fd:*从给定的文件描述符中读取输入行
  • *-r:按原样处理 \ *字符(不能用于转义特殊字符)
  • -t timeout:尝试在给定的秒数内读取输入
  • -N:从输入中准确读取 N 个字符,除非发生超时或达到EOF

3.1. 从文件中读取

当我们想要处理文件中的特定字段时,从文件中读取是非常有用的

让我们看一下汽车的一些简单的结构化数据:

car,car model,car year,car vin
Mercury,Grand Marquis,2000,2G61S5S33F9986032
Mitsubishi,Truck,1995,SCFFDABE1CG137362

现在让我们考虑从这个CSV文件中提取特定字段并打印出特定字段:

$ {
      exec {file_descriptor}<"./file.csv"
      declare -a input_array
      while IFS="," read -a input_array -u $file_descriptor 
          do
             echo "${input_array[0]},${input_array[2]}" 
          done
      exec {file_descriptor}>&-
  }

在这个例子中,我们首先调用exec 内置 bash 命令来打开我们输入的文件描述符。然后,我们在while 循环中将其传递给read命令,允许我们逐行读取文件

最后,我们通过再次调用exec命令来关闭文件描述符。

请注意,**我们将IFS定义为while 循环的一部分。**这确保了循环外的进一步分词操作将使用默认的IFS

这给了我们以下输出:

car,car year
Mercury,2000
Mitsubishi,1995

我们在开头提到我们可以更改默认输入行分隔符。

让我们考虑使用分号分隔的行而不是换行字符来构造输入的情况:

car,car model,car year,car vin; \
> Mercury,Grand Marquis,2000,2G61S5S33F9986032; \
> Mitsubishi,Truck,1995,SCFFDABE1CG137362;

我们现在修改我们的函数以考虑新的行分隔符:

$ {
      # same calls as before
      while IFS="," read -a input_array -d ";"  -u $file_descriptor 
      # same calls as before
  }

这提供了与以前相同的输出。

3.2. 从其他命令中读取

我们还可以通过**管道重定向将 read命令与其他 Linux 命令结合使用**。

让我们重定向ls 命令的输出并从* ⁄根)*文件夹中提取文件名及其访问权限  :

$ { 
      ls -ll / | { declare -a input
                   read
                   while read -a input; 
                   do
                       echo "${input[0]} ${input[8]}"
                   done }
  }

每个 Linux 发行版的输出都不同,但是,我们可以看到截断的输出,正如我们所期望的那样:

drwxr-xr-x bin
drwxr-xr-x boot
drwxr-xr-x dev
# some more folders

让我们深入了解一下这个例子。首先,我们执行*ls -ll /*命令打印根目录下的文件。

然后,我们将输出通过管道传输到调用read的命令组 的标准输入

这样,我们就可以迭代处理多行输入,并打印每行的第一列和第八列。

我们还在循环之前执行一次read以跳过ls在顶部打印的摘要。当我们想要避免表头时,这非常有用。

3.3. 超时和特殊字符

在复杂的脚本中,我们可能需要更大的灵活性来避免阻塞read调用。 此外,输入可能包含我们不想转义的特定 \ 字符(例如在生成的密码中):

{
    prompt="You shall not pass:"
    read -p "$prompt" -s -r -t 5 input
        if [ -z "$input" ]; then
            echo -e "\ntimeout occured!"
        else
            echo -e "\ninput word [$input]"
        fi
}

请注意,我们使用了*-t选项并指定了 5 秒的超时时间。这确保了如果在此时间间隔内没有输入任何输入(包括默认的换行*符),read命令将自动返回。

由于我们说过反斜杠应该按字面意思理解,因此不再可能像在前面的示例中那样跨多行输入输入:

You shall not pass: # invisible input here
input word [blogdemo\is]

3.4. 准确读取 N 个字符

让我们把事情进一步复杂化,假设我们希望输入中正好有 11 个字符:

{
    prompt="Reading exactly 11 chars:"
    read -p "$prompt" -N 11 -t 5 input1 input2 
    echo -e "\ninput word1 [$input1]"
    echo "input word2 [$input2]"
}

当我们运行上面的例子时,输出有点令人惊讶:

Reading exactly 11 chars:blogdemo is # no <newline> here
input word1 [blogdemo is]
input word2 []

引入 -N 选项会导致三个主要副作用

首先,行分隔符不再重要read命令将等待输入的确切字符数退出(因此在这里设置超时也是有意义的)。

其次,它不再将输入拆分为单词,因为我们希望将 11 个字符分配给input1

最后,如果发生超时,  read甚至会将部分输入分配给input1变量。