Bash 如何将每个列的值指定给其名称?

Bash 如何将每个列的值指定给其名称?,bash,csv,syntax,Bash,Csv,Syntax,我有一个MetaData.csv文件,其中包含许多用于执行分析的值。我想要的是: 1-读取列名并使变量与列名相似。 2-将每列中的值作为可由其他命令读取的整数放入变量中。列\名称=其\值 MetaData.csv: MAF,HWE,Geno_Missing,Inds_Missing 0.05,1E-06,0.01,0.01 我编写了以下代码,但效果不好: #!/bin/bash Col_Names=$(head -n 1 MetaData.csv) # Cut header (camma se

我有一个MetaData.csv文件,其中包含许多用于执行分析的值。我想要的是: 1-读取列名并使变量与列名相似。 2-将每列中的值作为可由其他命令读取的整数放入变量中。列\名称=其\值

MetaData.csv:

MAF,HWE,Geno_Missing,Inds_Missing
0.05,1E-06,0.01,0.01
我编写了以下代码,但效果不好:

#!/bin/bash
Col_Names=$(head -n 1 MetaData.csv) # Cut header (camma sep)
Col_Names=$(echo ${Col_Names//,/ }) # Convert header to space sep
Col_Names=($Col_Names) # Convert header to an array 

for i in $(seq 1 ${#Col_Names[@]}); do
N="$(head -1 MetaData.csv | tr ',' '\n' | nl |grep -w 
"${Col_Names[$i]}" | tr -d " " | awk -F " " '{print $1}')";
${Col_Names[$i]}="$(cat MetaData.csv | cut -d"," -f$N | sed '1d')";
done
输出:

HWE=1E-06: command not found
Geno_Missing=0.01: command not found
Inds_Missing=0.01: command not found
cut: 2: No such file or directory
cut: 3: No such file or directory
cut: 4: No such file or directory
=: command not found
Fnames=19.vcf.gz
MAF=0.05
Fnames=20.vcf.gz
MAF=
Fnames=21.vcf.gz
MAF=
Fnames=22.vcf.gz
MAF=
19.vcf.gz 20.vcf.gz 21.vcf.gz 22.vcf.gz
MAF=0.05
HWE=1E-06
Geno_Missing=0.01
Inds_Missing=0.01
MAF=0.05 HWE=1E-06 Geno_Missing=0.01 Inds_Missing=0.01
预期产出:

MAF=0.05
HWE=1E-06
Geno_Missing=0.01
Inds_Missing=0.01
问题:

1-我想使用数组长度(${Col#u Names[@]})作为最终迭代,即5,但数组索引从0(0-4)开始。所以MAF列没有被循环捕获。循环也会迭代两次(一次是0-4,另一次是2-4!)。 2-当我试图调用变量中的值(echo$MAF)时,它们是空的


任何解决方案都非常受欢迎。

我不认为您可以在Bash中实现一个健壮的CSV读取器/解析器,但您可以在一定程度上实现它,使其与简单的CSV文件一起工作。例如,一个非常简单的
bash
实现的CSV可能如下所示:

#/bin/bash
set-e
行数='0'
标题=()
当IFS=','读取-ra行时;做
如果测试“$ROW_NUMBER”=“0”;然后
对于((I=0;I<${行[@]};I++);做
标题[“$I”]=“${ROW[I]}”
完成
其他的
声明-数据行映射
对于((I=0;I<${行[@]};I++);做
数据行映射[${HEADERS[“$I”]}]=“${ROW[I]}”
完成
#演示{
echo-e“${DATA_ROW_MAP['Fnames']}\t${DATA_ROW_MAP['Inds_Missing']}”
#}演示
取消设置数据行映射
fi
行号=$((行号+1))
完成
请注意,is有多个缺点:

  • 它只适用于
    分隔字段(真正的“C”SV)
  • 不能处理多行记录
  • 它不能处理野外逃逸
  • 它认为第一行始终表示标题行
这就是为什么许多命令可能会产生和使用
\0
-分隔的数据,仅仅因为此控制字符可能更易于使用。现在我不确定的是
test
是否是
bash
执行的唯一外部命令(我相信是的,但它可能可以使用
case
重新实现,这样就不会执行外部
test

使用示例(带有演示输出):

/read-csv.sh

我不建议使用这个解析器,但建议使用更面向CSV的工具(Python可能是最容易使用的选择;+或者如果您最喜欢的语言,如您所述,是R,那么这可能是您的另一个选择:)。

如果我正确理解您的需求,请您尝试一下:

#!/bin/bash

nr=1                                    # initialize input line number to 1
while IFS=, read -r -a ary; do          # split the line on "," then assign "ary" to the fields
    if (( nr == 1 )); then              # handle the header line
        col_names=("${ary[@]}")         # assign column names
    else                                # handle the body lines
        for (( i = 0; i < ${#ary[@]}; i++ )); do
            printf -v "${col_names[i]}" "${ary[i]}"
                                        # assign the variable "${col_names[i]}" to the input field
        done
        # now you can access the values via its column name
        echo "Fnames=$Fnames"
        echo "MAF=$MAF"
        fname_list+=("$Fnames")         # create a list of Fnames
    fi
    (( nr++ ))                          # increment the input line number
done < MetaData.csv
echo "${fname_list[@]}"                 # print the list of Fnames
  • statetemt
    IFS=,read-a ary
    主要相当于 前三行;它在“,”上拆分输入,并指定 将数组变量
    ari
    添加到字段值
  • 有几种方法可以将变量的值用作变量名 (间接变量引用)
    printf-v VarName Value
    就是其中之一
[编辑]

根据OP的更新输入文件,以下是另一个版本:

#!/bin/bash

nr=1                                    # initialize input line number to 1
while IFS=, read -r -a ary; do          # split the line on "," then assign "ary" to the fields
    if (( nr == 1 )); then              # handle the header line
        col_names=("${ary[@]}")         # assign column names
    else                                # handle the body lines
        for (( i = 0; i < ${#ary[@]}; i++ )); do
            printf -v "${col_names[i]}" "${ary[i]}"
                                        # assign the variable "${col_names[i]}" to the input field
        done
    fi
    (( nr++ ))                          # increment the input line number
done < MetaData.csv

for n in "${col_names[@]}"; do          # iterate over the variable names
    echo "$n=${!n}"                     # print variable name and its value
done

# you can also specify the variable names literally as follows:
echo "MAF=$MAF HWE=$HWE Geno_Missing=$Geno_Missing Inds_Missing=$Inds_Missing"
至于输出,前四行由
echo“$n=${!n}”
打印,最后一行由
echo“MAF=$MAF…”
打印。
您可以根据以下代码中变量的使用情况选择任一语句。

这将根据您发布的示例输入生成您发布的预期输出:

$ awk -F, -v OFS='=' 'NR==1{split($0,hdr); next} {for (i=1;i<=NF;i++) print hdr[i], $i}' MetaData.csv
MAF=0.05
HWE=1E-06
Geno_Missing=0.01
Inds_Missing=0.01

$awk-F,-vofs='=''NR==1{split($0,hdr);next}{for(i=1;iCopy/paste您的脚本,并修复它告诉您的问题。话虽如此,您发布的shell脚本中似乎没有任何内容不应该在一次调用awk中处理,因此如果您想帮助您以正确的方式完成任何事情,请发布一条消息,以便我们可以帮助您。@Ed Morton,i使用了shellcheck.net,但找不到问题。我提供了数据(MetaData.csv)在问题结束后,请将其复制并粘贴到文件中。提前感谢您的回复。假设
MetaData.csv
是示例输入,您忘记发布预期输出。我真的不希望shellcheck.net能够完全解决您的问题,只需帮助您将您的代码添加到我们不需要查看代码的地方即可h shellcheck可以检测到明显的问题,这样我们就可以专注于剩下的任何问题。因此,请再次通过shellcheck运行您的代码,修复它告诉您的问题,然后将其作为问题中的代码发布,而不是带有所有明显问题的当前代码。@Ed Morton,我使用shellcheck.net再次检查了代码。它只是d一些没有改变输出的小修改,例如:对于i in
seq 1${{Col#u Names[@]}
;>>对于i in$(seq 1${Col#u Names[@]});预期输出:MAF=0.5 HWE=1E-06 Geno_Missing=0.01 Inds_Missing=0.01。但是,根据评论,我正在考虑使用另一种语言来完成这项工作。谢谢。请修复shellcheck告诉您的所有问题,而不仅仅是其中的一些问题。无论如何,如果您在问题中发布预期输出,我们可能会开始尝试帮助您解决这些问题o解决你的问题,而不是试图帮助你问你的问题。亲爱的Fluffy,感谢你的时间和努力,我用不同的代码完成了这项工作,但它并不像我希望的那样自动化。我将尝试解决问题。关于使用Python或R,问题是,我如何在不编写Python的情况下将值作为变量输入bashn/R的输出并再次读取到bash?谢谢请查看一些原因,如果需要管理Unix工具,为什么应该使用awk脚本而不是shell读取循环来执行此操作。感谢您的回答,我需要[colname=its value]作为变量。似乎无法对第一列(Fnames)执行此操作,但对于其余的列,这是可能的,因为每列只有一个
$ awk -F, -v OFS='=' 'NR==1{split($0,hdr); next} {for (i=1;i<=NF;i++) print hdr[i], $i}' MetaData.csv
MAF=0.05
HWE=1E-06
Geno_Missing=0.01
Inds_Missing=0.01