Make Bash script exit and print error message if users invoke the script incorrectly
Asked Answered
M

4

5

Script needed was

#!/bin/bash

# Check if there are two arguments
if [ $# -eq 2 ]; then
   # Check if the input file actually exists.
   if ! [[ -f "$1" ]]; then
     echo "The input file $1 does not exist."
     exit 1
   fi
else
   echo "Usage: $0 [inputfile] [outputfile]"
   exit 1
fi

# Run the command on the input file
grep -P "^[\s]*[0-9A-Za-z-]+.?[\s]*$" "$1" > "$2"

Edit, the script has changed to

grep -P "^[\s]*[0-9A-Za-z-]+.?[\s]*$" $*
if [ ! -f "$1" ]; then

  echo 'Usage: '
  echo
  echo './Scriptname inputfile > outputfile'
  exit 0

fi

invoking the script with no parameters gives no erros and sits blank

Usage: 

./Scriptname inputfile > outputfile

I have bit of code

grep -P "^[\s]*[0-9A-Za-z-]+.?[\s]*$" $*

This code pulls lines that have a single word on them and pumps the output to a new file, so for example

This is a multi word line
this
the above line is not
now
once again wrong

The output would be

This
now

The code works, users invoke the code using ./scriptname file > newfile

However, I am trying to expand the code to give users an error message if they invoke the script incorrectly.

For the error messange, I'm thinking of echoing something back like scriptname file_to_process > output_file.

I did try

if [incorrectly invoted unsure what to type]
echo $usage

exit 1
Usage="usage [inputfile] [>] [outputfile]

However I have had little luck. The code runs but does nothing if I invoke with just the script name. Also, if I invoke the script with just the scriptname and the input file, it will output the results instead of exiting with the error message.

Other ones I have tried are

if [ ! -n $1 ]; then

  echo 'Usage: '
  echo 
  echo './Scriptname inputfile > outputfile'
  exit 0

fi

Given replies I have received so far, my code now is

#!/bin/bash
grep -P "^[\s]*[0-9A-Za-z-]+.?[\s]*$" $*
if [ ! -f "$1" ]; then

  echo 'Usage: '
  echo 
  echo './Scriptname inputfile > outputfile'
  exit 0

fi

When invoking the script without an input file the script does nothing and has to be aborted with ctrl+c, still trying to get the echo of the invoke message.

Memoirs answered 19/10, 2012 at 2:42 Comment(4)
Are you using /bin/bash or /bin/sh?Coverup
Use "$@" rather than $* on the command line; that preserves the spaces in file name arguments with spaces. If you use grep -P -e "...your regex..." with the -e preceding the expression, then users can type things like -n or -l as an option on the command line, and grep will modify its behaviour appropriately. (On systems where GNU getopt() is used to permute arguments, you may not need to specify -e; I am not keen on that feature.)Bichromate
Ok thnaks for that I will change the $@ nowMemoirs
Instead of [ ! -n $1 ] use [ -z "$1" ]. Also put the grep after the parameter count test, not before. If it's before and there is no parameter it will sit there waiting for you to type input on stdin. To experiment, try grep x at the command line without a file name and type in some lines with and without x in them, then ^D to exit.Leomaleon
P
3

When you are invoking the script like ./scriptname file > newfile, the shell interprets file as the only argument to ./scriptname. This is because > is the standard output redirection operator.

I would like to propose 2 possible alternatives:


Alternative 1: Maybe you're can try passing it as 1 argument like this?
./scriptname 'file > newfile'

In that case one way to check the format would be

#!/bin/bash

# Check if the format is correct
if [[ $1 =~ (.+)' > '(.+) ]]; then
  # Check if the input file actually exists.
  if ! [[ -f "${BASH_REMATCH[1]}" ]]; then
    echo "The input file ${BASH_REMATCH[1]} does not exist!"
    exit 1
  fi
else
  echo "Usage: $0 \"[inputfile] [>] [outputfile]\""
  exit 1
fi

# Redirect standard output to the output file
exec > "${BASH_REMATCH[2]}"
# Run the command on the input file
grep -P "^[\s]*[0-9A-Za-z-]+.?[\s]*$" "${BASH_REMATCH[1]}"

Note: If you are checking whether the arguments are valid or not, it's generally better to run commands only after the checking is done.


Alternative 2: Passing 2 arguments like

./scriptname file newfile

The script looks like this

#!/bin/bash

# Check if there are two arguments
if [ $# -eq 2 ]; then
   # Check if the input file actually exists.
   if ! [[ -f "$1" ]]; then
     echo "The input file $1 does not exist."
     exit 1
   fi
else
   echo "Usage: $0 [inputfile] [outputfile]"
   exit 1
fi

# Run the command on the input file
grep -P "^[\s]*[0-9A-Za-z-]+.?[\s]*$" "$1" > "$2"
Pressroom answered 19/10, 2012 at 7:37 Comment(2)
Thank you, the only issue is the output is Usage: $0 [inputfile] [>] [outputfile] so I would just need to add another else after the final fi and an exit 0 correct?Memoirs
The script will run grep and if grep runs successfully, the script will exit with status 0, so you don't actually need exit 0 in the script (if I am understanding what you are saying...)Pressroom
P
3

I'd use parameter expansion for this:

inputfile=${1:?Usage: $(basename $0) inputfile > outputfile}

If the script is called without arguments (i.e. $1 is unset) the ${var:?error message} expansion causes the shell to display an error with the given message and exit. Otherwise the first argument is assigned to $inputfile.

Pep answered 19/10, 2012 at 8:21 Comment(2)
Good question - for simple scripts it is an elegant one-line solution and works well for me, thanks!Godevil
Also seems you can split the error message over lines in your script and the output will reflect the same line-breaks, if you needed to format the Usage more clearlyGodevil
C
1

Try to add double quotes around $1 and use -f to check for exists and is normal file:

if [ ! -f "$1" ]; then

  echo 'Usage: '
  echo 
  echo './Scriptname inputfile > outputfile'
  exit 0

fi
Coverup answered 19/10, 2012 at 2:49 Comment(1)
Ok I tired that using grep -P "^[\s]*[0-9A-Za-z-]+.?[\s]*$" $* if [ ! -f "$1" ]; then echo 'Usage: ' echo echo './Scriptname inputfile > outputfile' exit 0 fi The code when I invoked it just sat there an did nothing, I tried invoking with just the script name, have I missed something in using the code.Memoirs
C
1

Also you can check for the param count with $# and cat an usage message:

if [ ! $# -eq 1 ]; then
cat << EOF
    Usage:

    $0 'input_file' > output_file
EOF
    exit 1
fi
Clicker answered 19/10, 2012 at 2:55 Comment(1)
Instead of [ ! $# -eq 1 ] use [ $# -ne 1 ]Leomaleon

© 2022 - 2024 — McMap. All rights reserved.