Contents

用逗号在bash中拆分列表

1. 概述

在 Linux 中,我们经常需要处理 CSV 格式的文件或文本。在本快速教程中,我们将探讨如何拆分 CSV 数据并将结果存储在 Bash 中。

2. 问题介绍

像往常一样,我们将通过一个例子来理解这个问题。首先,让我们看看我们的输入:

$ echo $INPUT 
Kotlin Corroutines,Java Stream API,Ruby On Rails,Other Language Features

如上面的输出所示,我们在变量$INPUT中存储了一行逗号分隔的值。敏锐的眼睛可能已经注意到输入字符串中的值包含空格

我们的目标是解析Bash中*$INPUT*变量的值。

如我们所见,输入是单行输入。我们将首先关注如何解决单行输入场景。

最后,我们将扩展解决方案以解决多行输入的情况。

3. 使用awk怎么样?

我们知道 awk 是 Linux 命令行中一个强大的文本处理工具。特别是,它擅长处理基于列的数据,例如 CSV。因此,例如,我们可以使用awk轻松解析*$INPUT 的* 值 :

$ awk -F',' '{ for( i=1; i<=NF; i++ ) print $i }' <<<"$INPUT"
Kotlin Corroutines
Java Stream API
Ruby On Rails
Other Language Features

正如我们在上面的awk命令中看到的,输入文本已被正确解析。

但是,值得一提的是,解析值存在于awk命令中。如果我们只想将输入文本转换为另一种格式,awk是一个不错的选择。但是,例如,如果我们想将每个解析的值保存在 shell 变量中以供进一步使用,我们需要在 shell 脚本中执行此操作。

4. 预处理数据,然后将它们存储在数组中

我们已经讨论过awk不能直接将解析结果提供给 shell。但是,由于它可以解析 CSV 数据并将其转换为多行格式,因此我们可以使用awk对输入进行转换,然后将多行值保存在一个数组中。

如果我们的 Bash 是 4 或更高版本,我们可以使用内置的 readarray 命令来读取awk命令的输出

$ readarray -t the_array < <(awk -F',' '{ for( i=1; i<=NF; i++ ) print $i }' <<<"$INPUT")
$ declare -p the_array
declare -a the_array=([0]="Kotlin Corroutines" [1]="Java Stream API" [2]="Ruby On Rails" [3]="Other Language Features")

我们使用 readarray命令将预处理后的数据存储到数组变量the_array中。此外,正如declare命令的输出所示,the_array数组包含预期的数据。

readarray 命令很方便,但它不适用于较旧的 Bash 版本。

但是,read 命令在所有 Bash 版本中都可用。因此,我们可以调整IFS 变量并使用read命令将预处理后的数据存储到数组变量中

$ IFS=$'\n' read -r -d '' -a the_array2 < <(awk -F',' '{ for( i=1; i<=NF; i++ ) print $i }' <<<"$INPUT")
$ declare -p the_array2
declare -a the_array2=([0]="Kotlin Corroutines" [1]="Java Stream API" [2]="Ruby On Rails" [3]="Other Language Features")

值得一提的是,IFS变量更改只会为紧随其后的读取语句设置变量。它根本不会干扰当前的 shell 环境。

5.设置IFS=,

在前面的示例中,我们设置了 IFS=$’\n’并使用 read命令将预处理后的数据存储到数组中。或者,我们可以设置IFS=,(逗号)并直接从原始输入中读取每个值。

例如,如果我们知道字段的数量,我们可以读取每个值并将其分配给一个单独的 shell 变量

$ cat four_fields.sh
#!/bin/bash
INPUT="Kotlin Corroutines,Java Stream API,Ruby On Rails,Other Language Features"
IFS=, 
read KOTLIN JAVA RUBY OTHER <<<$INPUT
#Verify the result
echo 'Var $KOTLIN has the value:'$KOTLIN
echo 'Var $JAVA has the value:'$JAVA
echo 'Var $RUBY has the value:'$RUBY
echo 'Var $OTHER has the value:'$OTHER

为了便于演示,我们将所有内容都放在名为four_fields.sh的shell 脚本中。在这个小脚本中,我们设置*IFS=,。*然后,我们知道,输入中有四个字段。接下来,我们将每个值分配给一个单独的变量。最后,我们打印四个变量的值来验证输入是否被正确解析。

现在,让我们执行脚本:

$ ./four_fields.sh
Var $KOTLIN has the value:Kotlin Corroutines
Var $JAVA has the value:Java Stream API
Var $RUBY has the value:Ruby On Rails
Var $OTHER has the value:Other Language Features

显然,这种方法按预期工作。

如果我们不知道字段的数量,我们仍然可以设置IFS=,并使用read将值存储到数组中

$ cat ./fields_in_array.sh
#!/bin/bash
INPUT="Kotlin Corroutines,Java Stream API,Ruby On Rails,Other Language Features"
IFS=,
read line <<<$INPUT
FIELDS=( $line )
#verify the result
declare -p FIELDS
$ ./fields_in_array.sh
declare -a FIELDS=([0]="Kotlin Corroutines" [1]="Java Stream API" [2]="Ruby On Rails" [3]="Other Language Features")

如上例所示,我们可以使用read命令解析 CSV 数据并将值存储在数组中,方法是设置IFS=,。

6. 当输入为多行 CSV 文件时

到目前为止,我们已经探索了如何解析和存储单行 CSV 输入。如果我们需要处理多行 CSV 文件,我们可以使用while循环来扩展解决方案。

让我们看一个例子:

$ cat input.csv
Kotlin Corroutines,Java Stream API,Ruby On Rails,Other Language Features
Kotlin Corroutines 2,Java Stream API 2,Ruby On Rails 2,Other Language Features 2
Kotlin Corroutines 3,Java Stream API 3,Ruby On Rails 3,Other Language Features 3

现在,我们有一个包含三行的 CSV 文件input.csv

让我们看看如何使用whileread解析每一行:

$ cat ./fields_in_file.sh
#!/bin/bash
IFS=,
while read line; do
  FIELDS=( $line )
  # using the array to do further operations
  #....
  declare -p FIELDS
done < input.csv
$ ./fields_in_file.sh
declare -a FIELDS=([0]="Kotlin Corroutines" [1]="Java Stream API" [2]="Ruby On Rails" [3]="Other Language Features")
declare -a FIELDS=([0]="Kotlin Corroutines 2" [1]="Java Stream API 2" [2]="Ruby On Rails 2" [3]="Other Language Features 2")
declare -a FIELDS=([0]="Kotlin Corroutines 3" [1]="Java Stream API 3" [2]="Ruby On Rails 3" [3]="Other Language Features 3")

正如我们所见,脚本fields_in_file.sh使用 while循环扩展了之前的 field_in_array.sh

在解析一行并将值存储在数组FIELDS之后,我们只需通过declare命令打印FIELDS的内容。但是,我们可以使用数组在现实世界中执行一些有意义的任务。

当我们执行脚本时,我们可以看到它打印了预期的输出。