how to pass a file as stdin in a shell script

2019-08-15 04:52发布

问题:

I have a bash script which works when called like this: ./stats.sh -rows test_file

The program basically calculates row averages and medians & column averages and medians. Now to the program I want to pass file as standard input. but when I am running this code it prints "you have 2 provide 2 arguments". what changes should I have to make so that the code takes stdin as a file. I mean to say If I want to run the script I can run it by this way as well ./stats.sh -rows < test_file. I want to get this functionality!!

the input file is: (columns separated by tabs)

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

the code which I worked on is this:

#! /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

回答1:

Use the read a insight your bash script to read/process the content of the stdin.

Example:

skript.sh:

read a
echo "content of std in"
echo a

In this case cat test_file.txt | stats.sh will work.



回答2:

You may also redirect a file to stdin within your script:

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


回答3:

On many modern architectures, if a script absolutely requires you to pass a file name parameter, you can pass /dev/stdin to have standard input available as a file name-like entity. Many scripts also accept - as a special file name to mean "don't open a file; read standard input instead". Something like ./stats.sh -rows - <file might actually work, too. (Of course, that's a silly example, since ./stats.sh -rows file is equivalent; but it matters for something like stuff | ./stats.sh -rows -.)

However, the script suffers from numerous design flaws, and also has at least one syntax error (you cannot have a space in the assignment FILE= "${4}". The fourth argument will simply be evaluated as a command. This might even have security implications, depending on how you run this script). I would seriously consider replacing it entirely with one or two simple Perl or Awk scripts. The shell is just not very ideal for the sort of arithmetic you are tasking it with.



标签: linux bash shell