I want to handle filenames as arguments in a bash script in a cleaner, more flexible way, taking 0, 1, or 2 arguments for input and output filenames.
- when args = 0, read from stdin, write to stdout
- when args = 1, read from $1, write to stdout
- when args = 2, read from $1, write to $2
How can I make the bash script version cleaner, shorter?
Here is what I have now, which works, but is not clean,
#!/bin/bash
if [ $# -eq 0 ] ; then #echo "args 0"
fgrep -v "stuff"
elif [ $# -eq 1 ] ; then #echo "args 1"
f1=${1:-"null"}
if [ ! -f $f1 ]; then echo "file $f1 dne"; exit 1; fi
fgrep -v "stuff" $f1
elif [ $# -eq 2 ]; then #echo "args 2"
f1=${1:-"null"}
if [ ! -f $f1 ]; then echo "file $f1 dne"; exit 1; fi
f2=${2:-"null"}
fgrep -v "stuff" $f1 > $f2
fi
The perl version is cleaner,
#!/bin/env perl
use strict;
use warnings;
my $f1=$ARGV[0]||"-";
my $f2=$ARGV[1]||"-";
my ($fh, $ofh);
open($fh,"<$f1") or die "file $f1 failed";
open($ofh,">$f2") or die "file $f2 failed";
while(<$fh>) { if( !($_ =~ /stuff/) ) { print $ofh "$_"; } }
Best Answer
I'd make heavier use of I/O redirection:
Explanation
[[ $1 ]] && [[ ! -f $1 ]] && echo "file $1 dne" && exit 1
Test if an input file has been specified as a command line argument and if the file exists.
[[ $1 ]] && exec 3<$1 || exec 3<&0
If
$1
is set, i.e. an input file has been specified, the specified file is opened at file descriptor3
, otherwisestdin
is duplicated at file descriptor3
.[[ $2 ]] && exec 4>$2 || exec 4>&1
Similarly if the
$2
is set, i.e. an output file has been specified, the specified file is opened at file descriptor4
, otherwisestdout
is duplicated at file descriptor4
.fgrep -v "stuff" <&3 >&4
Lastly
fgrep
is invoked, redirecting itsstdin
andstdout
to the previously set file descriptors3
and4
respectively.Reopening standard input and output
If you'd prefer not to open intermediate file descriptors, an alternative is to replace the file descriptors corresponding to
stdin
andstdout
directly with the specified input and output files:A drawback with this approach is that you loose the ability to differentiate output from the script itself from the output of the command which is the target for the redirection. In the original approach, you can direct script output to the unmodified
stdin
andstdout
, which in turn might have been redirected by the caller of the script. The specified input and output files could still be accessed via the corresponding file descriptors, which are distinct from the scriptstdin
andstdout
.