Bash

讀取文件並儲存為數組而不跳過空字元串

  • June 14, 2021

File.tsv是一個製表符分隔的文件,有 7 列:

cat File.tsv
1   A   J               1
2   B   K   N           1
3   C   L   O   P   Q   1

以下讀取File.tsv的是具有 7 列的製表符分隔文件,並將條目儲存在數組 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[@]}
for ((i = 0; i < nA; i = i + 7)); do
   SlNo="${A[i]}"
   Artist="${A[$((i + 1))]}"
   VideoTitle="${A[$((i + 2))]}"
   VideoId="${A[$((i + 3))]}"
   TimeStart="${A[$((i + 4))]}"
   TimeEnd="${A[$((i + 5))]}"
   VideoSpeed="${A[$((i + 6))]}"
done

問題

tsv 文件中的某些條目是空的,但在讀取文件時會跳過空值。

筆記

是 tsv 文件,空值前面和後面都有一個製表符。

所需的解決方案

讀取空值並將其儲存在數組中。

正如我在評論中所說,這不是 shell 腳本的工作。bash(和類似的 shell)用於協調其他程序的執行,而不是用於處理數據。

改用任何其他語言 - awk、perl 和 python 都是不錯的選擇。它將更容易編寫,更容易閱讀和維護,而且速度更快。

這是一個範例,說明如何將文本文件讀入 中的雜湊數組 (AoH) perl,然後在各種列印語句中使用數據。

AoH 是一種資料結構,正如它的名字所說的那樣 - 一個數組,其中每個元素都是一個關聯數組(又名雜湊)。

順便說一句,這也可以使用數組數組 (AoA) 資料結構(也稱為 List of Lists 或 LoL)來完成,但是能夠通過欄位名稱訪問欄位而不是記住欄位編號會很方便.

您可以在 perl 隨附的 Perl Data Structures Cookbook 中閱讀有關 perl 資料結構的更多資訊。執行man perldscperldoc perldscperllol您可能也想閱讀perlreftut。如果您不perldata熟悉 perl 變數(“ Perl 具有三種內置數據類型:標量、標量數組和標量關聯數組,稱為雜湊”。“標量”是任何單個值,例如數字或字元串或對另一個變數的引用)

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;

僅供參考,正如@Sobrique 在評論中指出的那樣,主循環內my @line =...的整個循環可以只用一行程式碼替換(perl 有一些非常好的語法糖):foreach``while (<>)

 @record{@headers} = map { s/^\s+$//, $_ } split /\t/;

注意: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",
 },                  
]                     

注意嵌套的 for 循環如何以我們想要的確切順序列印資料結構(因為我們迭代了@headers數組),而只是用dd函式從Data::Dump輸出中轉儲它,輸出按鍵名排序的記錄(這就是 Data::Dump 處理的方式perl 中的雜湊沒有排序的事實)。


其他的建議

一旦您將數據保存在這樣的資料結構中,就可以輕鬆地將其插入 SQL 數據庫,如mysql / mariadbpostgresqlsqlite3。Perl 擁有所有這些以及更多的數據庫模組(參見DBI)。

(在 debian 等中,這些被打包為libdbd-mysql-perllibdbd-mariadb-perllibdbd-pg-perllibdbd-sqlite3-perllibdbi-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 上提供。

引用自:https://unix.stackexchange.com/questions/654103