1

I'm trying to make a command line tool out of a bash script. It is very simple:

grep ">" in_file >> out_file. 

So I want to copy all lines containing '>' from the first file to the second. I need a tool that I could run from the command line like this:

./tool.sh -input in_file -output out_file

-input and -output here are to keys.

I tried the code below:

while getopts "i:o:"
do
  case $Option in
          i)       input=$OPTARG;;
          o)     output=$OPTARG;; 
  esac
done

grep -n '>' input >> output

But I got an error: tool: input: No such file or directory.

What is wrong here ? And also I want make the 'in' key to be able to take more than one arguments. How can I do this ? I guess I just don't understand correctely how does the getopts work but I didn't find any good description.

1
  • Basic mistake -- you forgot the $ before the variable names. Commented Jul 9 at 17:49

3 Answers 3

2

I guess I just don't understand correctely how does the getopts work but I didn't find any good description.

In Bash getopts is a builtin:

$ type -a getopts
getopts is a shell builtin

Its behavior is described in help getopts:

$ help getopts
getopts: getopts optstring name [arg]
    Parse option arguments.

    Getopts is used by shell procedures to parse positional parameters
    as options.
    (...)
    Each time it is invoked, getopts will place the next option in the
    shell variable $name,

Your script doesn't work for 2 reasons - you use getopts incorrectly and you do not prepend variable names with $ to get their contents. It should be:

#!/usr/bin/env sh

while getopts "i:o:" Option
do
    echo in loop
    case "$Option" in
        i)     input="$OPTARG";;
        o)     output="$OPTARG";; 
    esac
done

echo input: "$input"
echo output: "$output"

grep -n '>' "$input" >> "$output"
Sign up to request clarification or add additional context in comments.

2 Comments

The script should also be launched with the correct name of the declared options, in this case ./tool.sh -i in_file -o out_file and not ./tool.sh --input in_file --oputput out_file, since input and output are variables names declared in the script, and not options names (declared in getopts parameter "i:o:")
Ok, thank you! But what if I want to make it possible to put several files in the input key ?
1

getopts doesn't natively support long options and a getopts long option solution would implement them with two hyphens and equals signs (like ./tool.sh --input=in_file --output=out_file). If you want single-hyphen long options, your best bet is to use a manual loop:

#!/bin/sh

die() {
  echo "$*" >&2  # report to standard error
  exit 2         # exit with failure code 2
}

input="/dev/stdin"    # default to reading standard input (like grep)
output="/dev/stdout"  # displaying the hits directly if not given an output

while [ $# -gt 0 ]; do
  case "$1" in
    ( -input )   input="$2"; shift 2 || die "Missing input file" ;;
    ( -output )  output="$2"; shift 2 || die "Missing output file" ;;
    ( * )        break ;;
  esac
done

grep -n ">" "$input" >> "$output"

I created a die function to make this more legible. Its exit ends the whole script (while return would simply issue that error to the place it is called).

This code only supports only have one input file, so ./tool.sh -input in_file1 -input in_file2 -output out_file will effectively ignore in_file1 because the loop reassigns $input with the second -input option.

To support multiple inputs with this syntax, use bash arrays:

#!/bin/bash

die() {
  echo "$*" >&2  # report to standard error
  exit 2         # exit with failure code 2
}

input=()
output="/dev/stdout"  # displaying the hits directly if not given an output

while [ $# -gt 0 ]; do
  case "$1" in
    ( -input )   input+=("$2"); shift 2 || die "Missing input file" ;;
    ( -output )  output="$2"; shift 2 || die "Missing output file" ;;
    ( * )        break ;;
  esac
done

grep -n ">" "${input[@]}" >> "$output"

Comments

0

Why not just do;

#!/bin/bash

grep ">" "$1" >> "out_file."

and then call like;

./tool.sh in_file

$1 is a positional paramater, meaning the next argument after the script becomes the input file name. If you wanted to allow the user of the script to specify an outout, use the second positional paramater;

#!/bin/bash

grep ">" "$1" >> "$2"

and call like;

./tool.sh in_file out_file.

To add more arguments, simply add more positional paramaters.

3 Comments

So that means that every time I have to rewrite the script to add as many parameters as I need. But I want to make it automatic.
ok try this: echo "$*" | xargs grep -h ">" >> "out_file." This will handle any number of arguments, and won't require editing later. $* takes all of the arguments you present (ie filenames), which is piped to xargs which turns them into something grep can use. -h strips the filenames from the output. If you have spaces in your filenames, use the -0 switch on xargs. This will also support the use of wildcards as your argument: ./tool.sh file*
$* smooshes arguments together in a way that drops information about their boundaries -- it means you can't tell the difference between ./myprogram "first argument" "second argument" and ./myprogram "first" "argument" "second" "argument"

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.