读取文件并存储为数组而不跳过空字符串

Por*_*ine 4 bash array csv read

File.tsv 是一个有 7 列的制表符分隔文件:

cat File.tsv
1   A   J               1
2   B   K   N           1
3   C   L   O   P   Q   1
Run Code Online (Sandbox Code Playgroud)

以下读取File.tsv7 列制表符分隔文件,并将条目存储在数组 A 中。

while IFS=$'\t' read -r -a D; do
    A=("${A[@]}" "${D[i]}" "${D[$((i + 1))]}" "${D[$((i + 2))]}" "${D[$((i + 3))]}" "${D[$((i + 4))]}" "${D[$((i + 5))]}" "${D[$((i + 6))]}")
done < File.tsv
nA=${#A[@]}
Run Code Online (Sandbox Code Playgroud)
while IFS=$'\t' read -r -a D; do
    A=("${A[@]}" "${D[i]}" "${D[$((i + 1))]}" "${D[$((i + 2))]}" "${D[$((i + 3))]}" "${D[$((i + 4))]}" "${D[$((i + 5))]}" "${D[$((i + 6))]}")
done < File.tsv
nA=${#A[@]}
Run Code Online (Sandbox Code Playgroud)

问题

tsv 文件中的某些条目为空,但在读取文件时会跳过空值。

笔记

是 tsv 文件,空值前后都有制表符。

所需的解决方案

读取空值并将其存储在数组中。

cas*_*cas 10

正如我在评论中所说,这不是 shell 脚本的工作。bash(和类似的 shell)用于协调其他程序的执行,而不是用于处理数据。

改用任何其他语言 - awk、perl 和 python 都是不错的选择。这将是更容易写,更容易阅读和维护,以及快。

下面是一个示例,说明如何将文本文件读入 中的哈希数组 (AoH) perl,然后在各种打印语句中使用这些数据。

AoH 是一种数据结构,正如它的名字所说的那样 - 一个数组,其中每个元素都是一个关联数组(又名哈希)。

顺便说一句,这也可以使用数组数组 (AoA) 数据结构(也称为列表列表或 LoL)来完成,但是能够通过字段名称访问字段而不必记住字段编号很方便.

您可以在 perl 随附的 Perl Data Structures Cookbook 中阅读有关 perl 数据结构的更多信息。运行man perldscperldoc perldsc。你可能还需要阅读perllolperlreftut太。而perldata如果你不熟悉Perl变量(“ Perl有三种内建的数据类型:标量,标量数组和标量的关联数组,被称为哈希” A‘标’是任何单一的价值,就像一个数或字符串或对另一个变量的引用

Perl 附带了大量文档和教程 - 运行man perl概述。包含的 perl 文档大约有 14MB,因此它通常位于单独的包中,以防您不想安装它。在 debian 上:apt install perl-doc. 此外,每个库模块都有自己的文档。

#!/usr/bin/perl -l

use strict;

# Array to hold the hashes for each record
my @data;

# Array of field header names.  This is used to insert the
# data into the %record hash with the right key AND to
# ensure that we can access/print each record in the right
# order (perl hashes are inherently unordered so it's useful
# and convenient to use an indexed array to order it)
my @headers=qw(SlNo Artist VideoTitle VideoId TimeStart TimeEnd VideoSpeed);

# main loop, read in each line, split it by single tabs, build into
# a hash, and then push the hash onto the @data array.
while (<>) {
  chomp;
  my %record = ();

  my @line = split /\t/;

  # iterate over the indices of the @line array so we can use
  # the same index number to look up the field header name
  foreach my $i (0..$#line) {
    # insert each field into the hash with the header as key.
    # if a field contains only whitespace, then make it empty
    ($record{$headers[$i]} = $line[$i]) =~ s/^\s+$//;
  }

  push @data, \%record ;
}

# show how to access the AoH elements in a loop:
print "\nprint \@data in a loop:";
foreach my $i (0 .. $#data) {
  foreach my $h (@headers) {
    printf "\$data[%i]->{%s} = %s\n", $i, $h, $data[$i]->{$h};
  }
  print;
}

# show how to access individual elements
print  "\nprint some individual elements:";
print $data[0]->{'SlNo'};
print $data[0]->{'Artist'};


# show how the data is structured (requires Data::Dump
# module, comment out if not installed)
print  "\nDump the data:";
use Data::Dump qw(dd);
dd \@data;
Run Code Online (Sandbox Code Playgroud)

仅供参考,正如@Sobrique 在评论中指出的那样,主循环内my @line =...的整个foreach循环while (<>)可以用一行代码替换(perl 有一些非常好的语法糖):

  @record{@headers} = map { s/^\s+$//, $_ } split /\t/;
Run Code Online (Sandbox Code Playgroud)

注意:Data::Dump是一个用于漂亮打印整个数据结构的 perl 模块。用于调试,并确保数据结构实际上是您认为的那样。而且,完全不同的是,输出的形式可以复制粘贴到 perl 脚本中并直接分配给变量。

它可用于libdata-dump-perl软件包中的 debian 和相关发行版。其他发行版可能也打包了它。否则从 CPAN 获取它。或者只是注释掉或删除脚本的最后三行——这里没有必要使用它,它只是打印输出循环中已经打印的数据的另一种方式。

将其另存为,例如,read-tsv.pl使其可执行chmod +x read-tsv.pl并运行它:

$ ./read-tsv.pl file.tsv                                    
print @data in a loop:
$data[0]->{SlNo} = 1
$data[0]->{Artist} = A
$data[0]->{VideoTitle} = J                        
$data[0]->{VideoId} = 
$data[0]->{TimeStart} = 
$data[0]->{TimeEnd} = 
$data[0]->{VideoSpeed} = 1

$data[1]->{SlNo} = 2
$data[1]->{Artist} = B
$data[1]->{VideoTitle} = K
$data[1]->{VideoId} = N
$data[1]->{TimeStart} = 
$data[1]->{TimeEnd} = 
$data[1]->{VideoSpeed} = 1

$data[2]->{SlNo} = 3
$data[2]->{Artist} = C
$data[2]->{VideoTitle} = L
$data[2]->{VideoId} = O
$data[2]->{TimeStart} = P
$data[2]->{TimeEnd} = Q
$data[2]->{VideoSpeed} = 1


print some individual elements:
1
A

Dump the data:
[
  {
    Artist     => "A",
    SlNo       => 1,
    TimeEnd    => "",
    TimeStart  => "",
    VideoId    => "",
    VideoSpeed => 1,
    VideoTitle => "J",
  },
  {
    Artist     => "B",
    SlNo       => 2,
    TimeEnd    => "",
    TimeStart  => "",
    VideoId    => "N",
    VideoSpeed => 1,
    VideoTitle => "K",
  },
  {
    Artist     => "C",
    SlNo       => 3,
    TimeEnd    => "Q",
    TimeStart  => "P",
    VideoId    => "O",
    VideoSpeed => 1,
    VideoTitle => "L",
  },                  
]                     
Run Code Online (Sandbox Code Playgroud)

注意嵌套的 for 循环如何以我们想要的确切顺序打印数据结构(因为我们遍历了@headers数组),而只是将它与dd函数一起从Data::Dump输出中转储到按键名排序的记录(这就是 Data::Dump 处理perl 中的散列没有排序的事实)。


其他的建议

一旦将数据放入这样的数据结构中,就很容易将其插入到 SQL 数据库中,例如mysql / mariadbpostgresqlsqlite3。Perl 具有用于所有这些以及更多的数据库模块(请参阅DBI)。

(在Debian等,这些被打包成libdbd-mysql-perllibdbd-mariadb-perllibdbd-pg-perllibdbd-sqlite3-perl,和libdbi-perl其他的发行版都会有不同的包名)

顺便说一句,主解析循环也可以使用另一个名为Text::CSV 的perl 模块来实现,它可以解析 CSV 和类似的文件格式,如 Tab 分隔。或者使用DBD::CSV,它Text::CSV允许您打开 CSV 或 TSV 文件并对其运行 SQL 查询,就像它是 SQL 数据库一样

事实上,使用这些模块将 CSV 或 TSV 文件导入 SQL 数据库是一个相当简单的 10-15 行脚本,其中大部分是样板设置的东西……实际算法是一个简单的 while 循环来运行一个对源数据进行 SELECT 查询并将 INSERT 语句插入到目标中。

这两个模块都为 debian 等打包,如libtext-csv-perllibdbd-csv-perl. 可能也打包用于其他发行版。并且一如既往地在 CPAN 上可用。

  • @jubilatious1 这不是**可接受**语言的列表。这只是示例语言的列表,是我所说的“使用**任何**其他语言”的一小部分。我没有反对 `raku`(我什至赞成你的答案,因为在这里看到使用 awk/perl/sed 以外的其他东西的答案很高兴,而且 raku 很酷),但不要觉得有必要只编辑我的答案再举一个例子——潜在的补充是无穷无尽的。 (4认同)
  • 顺便说一句,我忘了提及,您可以在 Perl Data Structures Cookbook 中阅读有关 perl 数据结构的更多信息。运行“man perldsc”或“perldoc perldsc”。你可能也想阅读 `perllol` 和 `perreftut`。perl 附带了大量文档和教程 - 运行`man perl` 以获得概述。 (2认同)
  • 您可以使用哈希切片机制通过 `my @record{@header} = split /\t/;` 大大简化您的 foreach 循环。https://perldoc.perl.org/perldata#Slices (2认同)