【问题标题】:Put lines of a text file in an array in bash将文本文件的行放在bash中的数组中
【发布时间】:2021-10-03 05:25:52
【问题描述】:

我正在从一位同事那里接过一个 bash 脚本,该脚本读取一个文件,处理它并根据 while 循环中的行打印另一个文件。

我现在需要为其添加一些功能。我现在遇到的问题是读取文件并将每一行放入一个数组中,除了该行的第二列可以为空,例如:

对于以\t 为分隔符的文本文件:

A\tB\tC
A\t\tC

对于相同但以, 作为分隔符的 CSV 文件:

A,B,C
A,,C

然后应该给

["A","B","C"] or ["A", "", "C"]

我接手的代码如下:

while IFS=$'\t\r' read -r -a col; do
# Process the array, put that into a file
lp -d $printer $file_to_print
done < $input_file

如果 B 被填充,这会起作用,但 B 现在有时需要为空,所以当输入文件保持为空时,创建的数组以及要打印的输出文件只是跳过这个空单元格(然后数组是 ["A","C"] )。

我尝试在 awk 上编写整个 bloc,但这带来了一系列问题,使得调用 lp 命令打印变得困难。

所以我的问题是,如何将行中的空单元格保存到我的 bash 数组中,以便我以后可以调用它并使用它?

非常感谢。我知道这可能很混乱,所以请询问,我会指定。

编辑:请求后,这是我尝试过的 awk 代码。这里的问题是它只打印最后一个打印请求,而我知道它会循环整个文件,并且 lp 命令仍在循环中。

awk 'BEGIN {
    inputfile="'"${optfile}"'"
    outputfile="'"${file_loc}"'"
    printer="'"${printer}"'"
    while (getline < inputfile){
      print "'"${prefix}"'" > outputfile
      split($0,ft,"'"${IFSseps}"'");
      if (length(ft[2]) == 0){
        print "CODEPAGE 1252\nTEXT 465,191,\"ROMAN.TTF\",180,7,7,\""ft[1]"\"" >> outputfile
        size_changer = 0
      } else {
        print "CODEPAGE 1252\nTEXT 465,191,\"ROMAN.TTF\",180,7,7,\""ft[1]"_"ft[2]"\"" >> outputfile
        size_changer = 1
      }
      if ( split($0,ft,"'"${IFSseps}"'") > 6)
        maxcounter = 6;
      else
        maxcounter = split($0,ft,"'"${IFSseps}"'");
      for (i = 3; i <= maxcounter; i++){
        x=191-(i-2)*33
        print "CODEPAGE 1252\nTEXT 465,"x",\"ROMAN.TTF\",180,7,7,\""ft[i]"\"" >> outputfile
      }
      print "PRINT ""'"${copies}"'"",1" >> outputfile
      close(outputfile)
      "'"`lp -d ${printer} ${file_loc}`"'"
    }
    close("'"${file_loc}"'");
  }'

EDIT2:继续尝试寻找解决方案,我尝试了以下代码但没有成功。这很奇怪,因为只执行 printf 而不将其放入数组中就可以保持格式不变。

$ cat testinput | tr '\t' '>'
A>B>C
A>>C

# Should normally be empty on the second ouput line
$ while read line; do IFS=$'\t' read -ra col < <(printf "$line"); echo ${col[1]}; done < testinput
B
C

【问题讨论】:

  • 根据您发布的信息,我们无法判断您为什么无法使用 Awk。也许更详细地解释它是如何不合适的,并向我们展示你做了什么;我猜这会比你当前的问题更容易解决。
  • 谢谢,我加了awk代码。

标签: arrays linux bash


【解决方案1】:

对于标签,它很复杂。

来自手册中的3.5.7 Word Splitting

IFS 空白字符序列也被视为分隔符。

由于 tab 是“IFS 空白字符”,因此将制表符序列视为单个分隔符

IFS=$'\t' read -ra ary <<<$'A\t\tC'
declare -p ary
declare -a ary=([0]="A" [1]="C")

您可以将制表符转换为非空白字符,假设它与字段中的实际数据不冲突:

line=$'A\t\tC'
IFS=, read -ra ary <<<"${line//$'\t'/,}"
declare -p ary
declare -a ary=([0]="A" [1]="" [2]="C")

为了避免与数据中的逗号发生冲突的风险,我们可以使用一个不常见的 ASCII 字符:FS,八进制 034

line=$'A\t\tC'
printf -v FS '\034'
IFS="$FS" read -ra ary <<<"${line//$'\t'/"$FS"}"

# or, without the placeholder variable
IFS=$'\034' read -ra ary <<<"${line//$'\t'/$'\034'}"

declare -p ary
declare -a ary=([0]="A" [1]="" [2]="C")

【讨论】:

  • 非常感谢 Glenn,这就是我需要的解决方案!作为一个额外的问题,您知道如何使用 sed 将制表符替换为 ASCII 吗?
  • 您仍然必须使用 shell 的 ANSI-C 引用:sed $'s/\t/\034/g' &lt;&lt;&lt;"$line" -- 但是为什么要调用 sed 来获取 shell 可以通过内置行为执行的操作?
  • 扩展知识的好奇问题。但是非常感谢你的帮助,你让我免于头痛好一阵子!
【解决方案2】:

一个使用参数扩展的bash 示例,我们将分隔符转换为\n 并让mapfile 在每一行中读取为新的数组条目...

对于制表符分隔数据:

for line in $'A\tB\tC' $'A\t\tC'
do
    mapfile -t array <<< "${line//$'\t'/$'\n'}"
    echo "############# ${line}"
    typeset -p array
done

############# A B       C
declare -a array=([0]="A" [1]="B" [2]="C")
############# A         C
declare -a array=([0]="A" [1]="" [2]="C")

注意:$'...' 构造确保 \t 被视为单个 &lt;tab&gt; 字符,而不是两个文字字符 \ + t

对于逗号分隔数据:

for line in 'A,B,C' 'A,,C'
do
    mapfile -t array <<< "${line//,/$'\n'}"
    echo "############# ${line}"
    typeset -p array
done

############# A,B,C
declare -a array=([0]="A" [1]="B" [2]="C")
############# A,,C
declare -a array=([0]="A" [1]="" [2]="C")

注意:这显然 (?) 假定所需数据不包含逗号 (,)。

【讨论】:

    【解决方案3】:

    这可能只是您的# Process the array, put that into a file 部分。

    IFS=, read -ra ray <<< "A,,C"
    for e in "${ray[@]}"; do o="$o\"$e\","; done
    echo "[${o%,}]"
    ["A","","C"]
    

    请参阅@Glenn 关于标签的出色回答。

    我的简单数据文件:

    $: cat x # tab delimited, empty field 2 of line 2
    a   b   c
    d       f
    

    我的测试:

    while IFS=$'\001' read -r a b c; do
      echo "a:[$a] b:[$b] c:[$c]"
    done < <(tr "\t" "\001"<x)
    a:[a] b:[b] c:[c]
    a:[d] b:[] c:[f]
    

    请注意,我使用了 ^A(一个 001 字节),但您也许可以使用像逗号或竖线 (|) 字符这样简单的字符。根据您的数据进行选择。

    【讨论】:

    • 嗨,保罗,谢谢您的回答。你能解释一下吗?我不确定如何使用它来读取文件,并使用制表符作为分隔符。
    • 已编辑。希望对您有所帮助。
    • 保持\001 并且不必跳整个^V + ^A 跳舞,IFS=$'\001' 怎么样?
    • 做任何对你有用的事。 CTRL 组合对我(个人而言)比输入整个七个字符的取消引用更容易,但如果我记得它,我还是会使用它,因为它可以更好地转换为复制/粘贴文本介质,解释更少。谢谢,马克 - 好电话。 :)
    猜你喜欢
    • 2021-02-03
    • 2019-03-30
    • 1970-01-01
    • 1970-01-01
    • 2013-08-07
    • 1970-01-01
    • 1970-01-01
    • 2020-06-28
    • 2015-09-08
    相关资源
    最近更新 更多