【问题标题】:how to pass a file as stdin in a shell script如何在 shell 脚本中将文件作为标准输入传递
【发布时间】:2015-04-17 09:32:01
【问题描述】:

我有一个 bash 脚本,可以像这样调用:./stats.sh -rows test_file

该程序基本上计算行平均值和中位数以及列平均值和中位数。现在到我想将文件作为标准输入传递的程序。但是当我运行这段代码时,它会打印出"you have 2 provide 2 arguments"。我应该进行哪些更改才能使代码将标准输入作为文件。我的意思是说如果我想运行脚本,我也可以通过这种方式运行它./stats.sh -rows < test_file。我想要这个功能!!

输入文件是:(由制表符分隔的列

93  93  93  93  93  93  93  93  100
73  84  95  83  72  86  80  97  100
85  0   82  75  88  79  80  81  100
85  0   87  73  88  79  80  71  100
80  81  83  63  100 85  63  68  100
53  57  61  53  70  61  73  50  100
55  54  41  63  63  45  33  41  100
53  55  43  44  63  75  35  21  100
100 100 100 100 100 100 100 100 100

我工作的代码是这样的:

#! /bin/bash
clear
#the arguments below will check for your command line args whether you have provided corrctly or not
flag=0
if [ "$#" -eq 0 ]; then
    echo "Please provide arguments"
elif [ "$#" -lt 2 ]; then
     echo "You have to provide 2 arguments" >&2
     exit 1
elif [ "$#" -gt 2 ]; then
    echo "${#}"
    FILE= "${4}"
    if [ -f "${FILE}" ]; then
     flag=1
    else
      echo "You have provided more number of arguments" >&2
    fi
    exit 1
else
    echo "You have entered correct number of arguments"
fi
# the below code is the case code which checks whether you have -r/-rows or -c/-cols
option="${1}"
l1=0
sorted=()
case ${option} in 
   -rows| -r| -r*)
      if [ $flag -eq 1 ]; then
        FILE="${4}"
      else
        FILE="${2}"
      fi
      clear
      echo "Average  Median"
      lines=$(wc -l < "$FILE")
      while read -r line
      do
      len=0
      tot=0
      name=$line
      #array=(`echo $name | cut -d "    "  --output-delimiter=" " -f 1-`)
      IFS=' ' read -a array <<< "$name"  #if any error comes that might be with this line just check the spaces in the speech marks they should be 4 spaces as it is checking for tabs
      for element in "${array[@]}"
      do
          tot=$(expr $tot + $element)
          #let tot+=$element #you can use this as well to get the totals
          let len+=1
      done
      avg=($(printf "%.0f" $(echo "scale=2;$tot/$len" | bc)))
      readarray -t sorted < <(for a in "${array[@]}"; do echo "$a"; done | sort)
      no=`expr $len % 2`
      if [ $no -eq 0 ]; then
      mid=`expr $len / 2`
      echo "$avg   ${sorted[$mid]}"
      else
      if [ $lines -lt 2 ]; then
        mid=`expr $len / 2`
            echo "$avg   ${sorted[$mid]}"
      else
        l1=`expr $len / 2`
        mid=`expr $l1 + 1`
        echo "$avg   ${sorted[$mid]}"
      fi

      fi
      unset "array[@]"
      unset "sorted[@]"
      done < "$FILE"
      ;;

   -cols| -c| -c*)
      if [ $flag -eq 1 ]; then
        FILE="${4}"
      else
        FILE="${2}"
      fi
      #echo "cols"
      #echo "File name is $FILE"
      cols=$(head -1 "$FILE" | tr "\t" '\n' | wc -l)
      lines=$(wc -l < "$FILE")
      IFS=$'\t\n' read -d '' -r -a lins < "$FILE"
      while read line;do
      x=1
      read -a array <<< "$line" ##Split the line by spaces
      for element in "${!array[@]}"
      do
      row[${element}]=$((${row[${element}]}+${array[$element]})) ##For each column increment array variable by number in the column.
      ((x++))
      done
      done < "$FILE"
      echo "Averages: "
      for element in ${row[@]}
      do
      mean= printf "%.0f" $(echo "scale=2;$element/$lines" | bc) ##bc prints floating point numbers and then we round of using scale and .0f
      echo -n "$mean    "
      done
      printf "\n"
      echo "Medians: "
      for ((i=0;i<$cols;i++))
      do 
      carr=()
      for ((j=i;j<$lines * $cols;j=j+$cols)) 
      do
          carr+=(${lins[$j]})
      done
    IFS=$' \n' csort=($(sort <<<"${carr[*]}"))
    no=`expr $lines % 2`
    if [ $no -eq 0 ]; then
           mid=`expr $lines / 2`
           echo -n "${csort[$mid]}    "
    else
           if [ $lines -lt 2 ]; then
                  mid=`expr $lines / 2`
              echo -n "${csort[$mid]}    "
           else
              l1=`expr $lines / 2`
              mid=`expr $l1 + 1`
              echo -n "${csort[$mid]}    "
           fi
    fi
      done <<<"$lins"
      printf "\n"

      ;; 
   *)  
      echo "`basename ${0}`:usage: [-r|-rows rows] | [-c|-cols columns]" 
      exit 1 # Command to come out of the program with status 1
      ;; 
esac 
trap "echo ;exit" 1 2

【问题讨论】:

  • 我不是 bash/shell 大师,但你不能只使用cat file.txt | stats.sh吗?
  • @Al.G.这显然是理想的,但该脚本旨在专门防止这种情况。

标签: linux bash shell


【解决方案1】:

您还可以在脚本中将文件重定向到标准输入:

# redirect FILE to stdin
exec 0<$FILE
# read from FILE
read VAR

【讨论】:

    【解决方案2】:

    使用read a 洞察您的 bash 脚本来读取/处理标准输入的内容。

    例子:

    skript.sh:

    read a
    echo "content of std in"
    echo a
    

    在这种情况下cat test_file.txt | stats.sh 可以工作。

    【讨论】:

      【解决方案3】:

      在许多现代架构中,如果脚本绝对需要您传递文件名参数,您可以传递 /dev/stdin 以使标准输入可用作类似文件名的实体。许多脚本还接受- 作为特殊文件名,表示“不要打开文件;而是读取标准输入”。像./stats.sh -rows - &lt;file 这样的东西也可能真的有用。 (当然,这是一个愚蠢的例子,因为./stats.sh -rows file 是等价的;但它对于stuff | ./stats.sh -rows - 之类的东西很重要。)

      但是,该脚本存在许多设计缺陷,并且还存在至少一个语法错误(在赋值 FILE= "${4}" 中不能有空格。第四个参数将被简单地评估为命令。这甚至可能具有安全性影响,取决于你如何运行这个脚本)。我会认真考虑用一两个简单的 Perl 或 Awk 脚本完全替换它。对于您要处理的那种算术,shell 并不是很理想。

      【讨论】:

      • 感谢您的回答和建议。我实际上为此使用 python,但我正在学习 bash,所以我只是用 bash 编写它
      猜你喜欢
      • 1970-01-01
      • 2013-07-10
      • 2017-12-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-23
      • 2018-04-09
      • 1970-01-01
      相关资源
      最近更新 更多