I'm trying to adapt a bash script from "Sams' Teach Yourself Linux in 24 Hours" which is a safe delete command called rmv. The files are removed by calling rmv -d file1 file2 etc. In the original script a max of 4 files can by removed using the variables $1 $2 $3 $4.
I want to extend this to an unlimited number of files by using a wildcard.
So I do:
for i in $*
do
mv $i $HOME/.trash
done
The files are deleted okay but the option -d of the command rmv -d is also treated as an argument and bash objects that it cannot be found. Is there a better way to do this?
Thanks,
Peter
#!/bin/bash
# rmv - a safe delete program
# uses a trash directory under your home directory
mkdir $HOME/.trash 2>/dev/null
# four internal script variables are defined
cmdlnopts=false
delete=false
empty=false
list=false
# uses getopts command to look at command line for any options
while getopts "dehl" cmdlnopts; do
case "$cmdlnopts" in
d ) /bin/echo "deleting: \c" $2 $3 $4 $5 ; delete=true ;;
e ) /bin/echo "emptying the trash..." ; empty=true ;;
h ) /bin/echo "safe file delete v1.0"
/bin/echo "rmv -d[elete] -e[mpty] -h[elp] -l[ist] file1-4" ;;
l ) /bin/echo "your .trash directory contains:" ; list=true ;;
esac
done
if [ $delete = true ]
then
for i in $*
do
mv $i $HOME/.trash
done
/bin/echo "rmv finished."
fi
if [ $empty = true ]
then
/bin/echo "empty the trash? \c"
read answer
case "$answer" in
y) rm -i $HOME/.trash/* ;;
n) /bin/echo "trashcan delete aborted." ;;
esac
fi
if [ $list = true ]
then
ls -l $HOME/.trash
fi
You can make use of shift here.
Once you find -d is one of the options in the switch, you can shift and get rid of -d from the positional parameters. Next you can
until [ -z $1 ] ; do
mv $1 $HOME/.trash
shift
done
getopts sets OPTIND to the index of the first argument after the options. (#)
So after parsing the options you can do:
shift $OPTIND-1
to remove the options from the argument list.
Then use "$#" rather than $*, and you can handle files with spaces in them.
Thanks a lot!
I changed the code to read:
#!/bin/bash
# rmv - a safe delete program
# todo: add ability to handle wildcards
# uses a trash directory under your home directory
mkdir $HOME/.trash 2>/dev/null
# four internal script variables are defined
cmdlnopts=false
delete=false
empty=false
list=false
# uses getopts command to look at command line for any options
while getopts "dehl" cmdlnopts; do
case "$cmdlnopts" in
d ) echo -e "deleting: \n" "${#:2}" ; delete=true ;;
e ) echo -e "emptying the trash..." ; empty=true ;;
h ) echo -e "safe file delete v1.0"
echo -e "rmv -d[elete] -e[mpty] -h[elp] -l[ist] file [...]" ;;
l ) echo -e "your .trash directory contains:" ; list=true ;;
esac
done
shift $OPTIND-1
if [ $delete = true ]
then
for i in $#
do
mv $i $HOME/.trash
done
echo "rmv finished."
fi
then
/bin/echo "empty the trash? \c"
read answer
case "$answer" in
y) rm -i $HOME/.trash/* ;;
n) /bin/echo "trashcan delete aborted." ;;
esac
fi
if [ $list = true ]
then
ls -l $HOME/.trash
fi
This deletes the files as desired but I get this error:
/home/peter/rmv: line 21: shift: 2-1: numeric argument required
mv: invalid option -- 'd'
Try `mv --help' for more information.
You need to use
shift $(($OPTIND - 1))
to get red of the processed command line args. Try this version:
#!/bin/bash
# rmv - a safe delete program
# uses a trash directory under your home directory
mkdir -p $HOME/.trash
# uses getopts command to look at command line for any options
while getopts "dehl" cmdlnopts; do
case "$cmdlnopts" in
d ) delete=true;;
e ) echo "emptying the trash..." ; empty=true ;;
h ) echo "safe file delete v1.0"
echo "rmv -d[elete] -e[mpty] -h[elp] -l[ist] files" ;;
l ) echo "your .trash directory contains:" ; list=true ;;
esac
done
shift $(($OPTIND - 1))
if [ -n "${delete}" ]; then
echo "deleting: " "${#}"
mv ${#} $HOME/.trash
echo "rmv finished."
fi
if [ -n "${empty}" ]; then
read -p "empty the trash? " answer
case "$answer" in
y) rm -i $HOME/.trash/* ;;
n) echo "trashcan delete aborted." ;;
esac
fi
if [ -n "${list}" ]; then
ls -l $HOME/.trash
fi
Late to the party, but for Googlers, this will generate the error Peter describes:
shift $OPTIND-1
while the syntax in Jurgen's reply will not:
shift $(($OPTIND-1))
The problem is that $OPTIND-1 is interpreted as a string, and shift can't use a string as an argument. $(( )) is Bash's arithmetic expansion operator. You put a string inside it, the string is evaluated as an arithmetic expression, and the value returned.
Related
This question already has an answer here:
Storing bash script argument with multiple values
(1 answer)
Closed 5 years ago.
I have been researching on using bash scripts to process command-line arguments. I have multiple optional arguments, each of which have one or more operands. An example is:
./script.sh -f file1 file2 -s server1 server2
-f itself is optional, but must be followed by filename; -s is optional, can be used without any operands or operands.
I know I can force putting "" on operands so I only deal with arguments with one operand and can use case $1 $2 shift to process it.
But I am interested in doing so without quotes, just to save some typing for the users.
A rough idea would be read in "$#" as one string, and separate them by space, then locate arguments with -/-- and assign operands following them. Maybe I can use an array to do that?
Any suggestions would be welcome.
Thanks folks for your wonderful suggestions. After spending some more time I resolved to the solution below:
Simply put, I use case and few checks to determine if the argument is an option or not. I use only alter flag variables during argument processing and then use the flags to determine what functions I will perform. In a way that I can have options in different order.
main(){
# flags, 1 is false, 0 is true. it's the damn bash LOCAL_DEPLOY=1
SERVER_DEPLOY=1 DRY_RUN=0
FILES=("${ALLOWEDFILES[#]}");
DEPLOYTARGET=("${ALLOWEDSERVERS[#]}");
if [ $# -eq 0 ]
then
printf -- "Missing optins, perform DRY RUN\nFor help, run with -h/--help\n"
for target in "${FILES[#]}"; do generate "$target"; done
echo "....dry run: markdown files generated in rendered/"
exit 0
fi
while true ; do
case "$1" in
-f |--file) # required operands
case "$2" in
"") die $1 ;;
*)
FILES=($2)
for i in "${FILES[#]}"; do
if is_option $i; then die $1; fi # check for option
if ! check_allowed $i ${ALLOWEDFILES[#]}; then exit 1; fi
done;
shift 2;; # input FILES are good
esac ;;
-l|--local) # no operands expected
DRY_RUN=1 # turn off dryrun
LOCAL_DEPLOY=0 # turn on local deploy
shift ;;
-s|--server) # optional operands
case "$2" in
"") shift ;;
*)
DEPLOYTARGET=($2) # use input
for i in "${DEPLOYTARGET[#]}"; do
if is_option $i; then die $1; fi # check for option
if ! check_allowed $i ${ALLOWEDSERVERS[#]}; then exit 1; fi
done ; shift 2;; # use input value
esac
DRY_RUN=1
SERVER_DEPLOY=0
;;
-n|--dryrun) # dry-run:generate markdown files only
DRY_RUN=0
shift ;;
-h|--help) # docs
print_help
exit 0
;;
--) shift; break ;;
-?*)
printf 'ERROR: Unkown option: %s\nExisting\n\n' "$1" >&2
print_help
exit 1
shift
;;
*)
break ;;
esac
done
echo "choose files: ${FILES[#]}"
echo ""
# dry-run
if [ $DRY_RUN == 0 ]; then
echo "..perform dry run.."
for target in "${FILES[#]}"; do generate "$target"; done
echo "....dry run: markdown files generated in rendered/"
exit 0
fi
# local-deploy
if [ $LOCAL_DEPLOY == 0 ] && [ $SERVER_DEPLOY != 0 ]; then
echo "..deploy locally"
for target in "${FILES[#]}"; do
generate "$target" > /dev/null
deploylocal "$target"
done;
# sync hexo-gcs hexo-yby
cd "$(dirname $HEXOLOCATION)"
./syncRepo.sh
printf -- "....hexo-gcs hexo-yby synced\n"
cd $CURRENTLOCATION
fi
# server-deploy
if [ $SERVER_DEPLOY == 0 ]; then
echo "..deploy on servers: ${DEPLOYTARGET[#]}"
echo ""
for target in "${FILES[#]}"; do # deploy locally
generate "$target" > /dev/null
deploylocal "$target"
done
# sync hexo-gcs hexo-yby
cd "$(dirname $HEXOLOCATION)"
./syncRepo.sh
printf -- "....hexo-gcs hexo-yby synced\n"
cd $CURRENTLOCATION
# deploy to selected server: git or gcp
for dt in "${DEPLOYTARGET[#]}"; do
deployserver $dt
done
fi
}
I have written a script that gets a variable number of arguments:
test.sh -i <input1> <input2> ... -o <output1> <output2> ...
I'm parsing the arguments as follows:
while [ $# -gt 1 ]; do
TMP=$(echo "$#" | cut -d '-' -f 2) #i <input1> <input2>
TMP1=$(echo "$TMP" | cut -d ' ' -f 1) #i
CNT=$(echo "$TMP" | wc -w) #3
set -x
case "$TMP1" in
i)
INPUTS=$(echo "$TMP" | cut -c 3-)
shift "$CNT"
;;
o)
OUTPUTS=$(echo "$TMP" | cut -c 3-)
shift "$CNT"
;;
esac
done
This works everytime, except for files that happen to have a '-' in their name.
Example:
./test.sh -i file1.txt file-2.txt -o out1.txt out-2.txt
Is there anyway I can force cut to ignore delimiters that occur within the file names?
You don't need all this string manipulation; each argument is already a separate word.
while (( $# > 0 )); do
case $1 in
-i) shift
while [[ $# -gt 0 && $1 != -* ]]; do
inputs+=( "$1" )
shift
done
;;
-o) shift
while [[ $# -gt 0 && $1 != -* ]]; do
outputs+=( "$1" )
shift
done
;;
*) echo "Unrecognized option $1"
exit 1
;;
esac
done
This can be refactored a little to avoid the repeated checks for running out of arguments.
for arg in "$#"; do
case $1 in
-i) mode=input; continue ;;
-o) mode=output; continue ;;
esac
case $mode in
input) input+=("$arg") ;;
output) output+=("$arg") ;;
*) echo "Unknown mode: $mode"
exit 1
;;
esac
done
Here's an alternative approach that may benefit someone.
The fact is, argument parsing is always a tradeoff, hence there's benefit in tailoring it to the application. Here's a pretty generic solution that allows a little bit of error checking and disorder in the arguments.
It's very simple, but I have added some example output and comments, and for the sake of readability and compatibility, stayed away from complex ways to save a line or two (especially on the if statements).
Sample Usage:
bash #> touch file-1 file3 file4 file-8 file7
bash #> argparse -i file-1 file3 file4 -c -k --q --j -r -t -o file-8 file7
Output:
Input files: file-1 file3 file4
Output files: file-8 file7
Args are: c k q j r t
Doing action for argument "c"
Doing action for argument "k"
Doing action for argument "j"
Script:
#!/bin/bash
#argparse
#Assign arrays
until [[ $# < 1 ]]; do
#ignore args "-i" and "-o", and tell the script to check for files following
if [ "$1" == "-i" ] ; then unset output ; input=1 ; shift
elif [ "$1" == "-o" ] ; then unset input ; output=1 ; shift
fi
#Add input and output files to respective arrays
if [ -f "$1" ] ; then
if [[ $input == 1 ]]; then
infiles+=($1)
elif [[ $output == 1 ]]; then
outfiles+=($1)
fi
else
#Add args to array
arg="$(echo "$1" | sed 's/-//g')"
args+=($arg)
fi
shift
done
#Some debug feedback
echo -e "Input files: ${infiles[#]}\nOutput files: ${outfiles[#]}\nArgs are: ${args[#]}\n"
#Simulate actually "doing" something with the args
for arg in "${args[#]}" ; do
case $arg in
"c") echo "Doing action for argument \"c\"" ;;
"k") echo "Doing action for argument \"k\"" ;;
"j") echo "Doing action for argument \"j\"" ;;
*) ;;
esac
done
Update/Edit: I've just realised, that the OP didn't have any requirement for parsing actual arguments other than -i and -o. Well regardless, this may still come in handy for someone at some point.
I was fiddling around with bash last month and am trying to create a script.
I want the script to search through the folders for files with some kind of extension defined by the argument -e. The folders are defined without -option. The output is 2 columns where in the first it prints the found files, and in the second the respective folders.
Is this the most efficient and/or flexible way to go?
I also can't manage to let the -l command work. Any idea what's wrong? When I enter -name \${CHAR}*, it simply doesn't work. Also, how can I make it recognize a range being used? With an if-function looking for the "-" character or something?
I think I managed to mount a block device, but how can I add the path as a parameter so it can be used as a folder? Setting a number as a var doesn't work, it tells me it doesn't recognize the command.
For some reason the 'no recursion' tag works, but the 'no numbers' doesn't. I have no idea why this would be different.
When using the 'no recursion' (nn) and 'no numbers' (nr) tags I use a long tag --tag for the arguments. Is it possible to use only 1 -tag? This is possible with get opts, but then I can't manage to use the other tags after the get opts has been used. Someone a solution?
Finally, is it possible, when finding 2 files with the same file name, instead of printing the file twice, can it just show the file once. But for every file with the same name keep a white space, so it can still show all the folders in the second column?
#!/bin/bash
#FUNCTIONS
#Error
#Also written to stderr
err() {
echo 1>&2;
echo "Error, not enough arguments" 1>&2;
echo "Usage: $0 [-e <file extension>] [<folder>]";
echo "Please enter the argument -e and at least 1 folder.";
echo "More: Please chek Help by using -h or --help.";
echo 1>&2;
exit
}
#Help
help() {
echo
echo "--- Help ---"
echo
echo "This script will look for file extentions in 1 or more directories. The output shows the found files with the according folder where it's located."
echo
echo "Argument -e <ext> is required."
echo "Other arguments the to-look-trough folders."
echo
echo "These are also usable options:"
echo "-h or --help shows this."
echo "-l <character> looks for files starting with the character."
echo "-l <character1>-<character2> does the same, but looks trough a range of characters."
echo "-b <block-device> mounts a partition to /mnt and let it search through."
echo "--nn (no numbers) makes sure there are no numbers in the file name."
echo "--nr (no recursion) doesn't look trough subdirectories."
echo "-r of –-err <file> writes the errors (f.e. corrupted directory) to <file>."
echo "-s <word> searches the word through the files and only shows the files having that word."
echo
exit
}
#VARS
#execute getopt
OPTS=$(getopt -o e:hl:b:r:s: -l "help,nn,nr,err" -n "FileExtensionScript" -- "$#");
#Bad arguments
if [ $? -ne 0 ];
then
err;
exit
fi
#Rearrange arguments
eval set -- "$OPTS";
#echo "AFTER SET -- \$OPTS: $#";
while true; do
case "$1" in
-e)
shift;
if [ -n "$1" ]; then
EXT=$1;
shift;
fi
;;
-h|--help)
shift;
help;
;;
-l)
shift;
if [ -n "$1" ]; then
CHAR=$1;
shift;
fi
;;
-b)
shift;
if [ -n "$1" ]; then
sudo mkdir /mnt/$1;
sudo echo -e "/dev/$1 /mnt/$1 vfat defaults 0 0 " >> /etc/fstab;
sudo mount -a;
999=/mnt/$1;
shift;
fi
;;
--nn)
shift;
NONUM=" ! -name '*[0-9]*'";
;;
--nr)
shift;
NOREC="-maxdepth 1";
;;
-f|--err)
shift;
if [ -n "$1" ]; then
ERROR="| 2>filename | tee -a $1";
shift;
fi
;;
-s)
shift;
if [ -n "$1" ]; then
SEARCH="-name '*$1*'";
shift;
fi
;;
--)
shift;
break;
;;
esac
done
#No folder or arguments given
if [ $# -lt 1 ];
then
err;
exit
fi
#Debug
echo "Folder argumenten: $#" >2;
echo \# $# >2;
#Create arrays with found files and according folders
FILES=( $(find $# $NOREC $SEARCH $NONUM -name \*.${EXT} $ERROR | rev | cut -d/ -f 1 | rev) )
FOLDERS=( $(find $# $NOREC $SEARCH $NONUM -name \*.${EXT} $ERROR | rev | cut -d/ -f 1 --complement | rev) )
#Show arrays in 2 columns
for ((i = 0; i <= ${#FILES[#]}; i++));
do
printf '%s %s\n' "${FILES[i]}" "${FOLDERS[i]}"
done | column -t | sort -k1 #Make columns cleaner + sort on filename
I am not native English speaker and am hoping to get some tips to finish my script :) Thanks in advance!
I'm writing my first bash script and having trouble assigning a file path to a variable:
$target="/etc/httpd/conf/httpd.conf"
It seems bash wants to interpret this with the "=" assignment operator resulting in the script throwing an error to the effect "No such file or directory."
Is there an easy way to do this? I've discovered I can assign a full path to a constant like this:
readonly TARGET=/etc/httpd/conf/httpd.conf
but that seems rather cumbersome. How would I perform string ops to modify/manipulate?
I've also discovered I can put full paths in an array like this:
declare -a cfile=('/root/.bashrc' '/etc/fstab')
All well and good, but how do I assign a file path to a variable?
== == == ==
finished! my first bash script - a basic config file manager
#!/bin/bash
# cfmgr.sh - configuration file manager bash script
# options: -get, -put
# '-get' creates SOURCEDIR/USERDIR and copies config files to USERDIR
# '-put' copies files in SOURCEDIR/USERDIR to system-defined locations on server
# purpose: helps with moving LAMP VMs to different hosts, bulk edits of
# of config files in editors like Notepad++, and backing up config files.
readonly SOURCEDIR=/usr/bin/_serverconfig
while [[ $# > 0 ]]
do
arg="$1"
shift
case $arg in
-put)
put=true
;;
-get)
get=true
;;
*)
badarg=true
;;
esac
done
clear
if [ $badarg ]; then
echo "Invalid argument. Use either 'scf.sh -put' or 'scf.sh -get' to put"\
"or get config files."
exit
elif [ $get ]; then
echo "Enter directory name to store files cfmgr will GET from this server:"
elif [ $put ]; then
echo "Enter directory name containing files cfmgr will PUT to this server:"
else
echo "Use either 'scf.sh -put' or 'scf.sh -get' to put or get config files."
exit
fi
read -e -i $SOURCEDIR"/" USERDIR
pattern=" |'"
if [[ $USERDIR =~ $pattern ]]; then
echo "Spaces not allowed. Please try again."
exit
fi
declare -a cfile=('/root/.bashrc' '/etc/fstab' '/etc/hosts' '/etc/networks'\
'/etc/php.ini' '/etc/nsswitch.conf' '/etc/ntp.conf' '/etc/resolv.conf'\
'/etc/sysctl.conf' '/etc/httpd/conf/httpd.conf' '/etc/selinux/config'\
'/etc/samba/smb.conf' '/etc/samba/smbusers' '/etc/security/limits.conf'\
'/etc/sysconfig/network' '/etc/sysconfig/network-scripts/ifcfg-eth0'\
'/etc/sysconfig/network-scripts/ifcfg-eth1')
if [ $get ]; then
if [[ -d "$USERDIR" ]]; then
echo $USERDIR "directory already exists. Please try again."
exit
else
mkdir -m 755 $USERDIR
fi
for file in ${cfile[#]}
do
if [ -e $file ]; then
rsync -q $file $USERDIR
if [ $? -eq 0 ]; then
sleep 0.1
printf "# "$file"\n"
fi
else
printf "not found: "$file"\n"
fi
done
elif [ $put ]; then
if [[ ! -d "$USERDIR" ]]; then
echo $USERDIR "directory does not exist. Please try again."
exit
fi
id=0
cd $USERDIR
for item in *
do
if [[ -f $item ]]; then
cdir[$id]=$item
id=$(($id+1))
fi
done
for file in ${cdir[#]}
do
case $file in
.bashrc)
idx=0
;;
fstab)
idx=1
;;
hosts)
idx=2
;;
networks)
idx=3
;;
php.ini)
idx=4
;;
nsswitch.conf)
idx=5
;;
ntp.conf)
idx=6
;;
resolv.conf)
idx=7
;;
sysctl.conf)
idx=8
;;
httpd.conf)
idx=9
;;
config)
idx=10
;;
smb.conf)
idx=11
;;
smbusers)
idx=12
;;
limits.conf)
idx=13
;;
network)
idx=14
;;
ifcfg-eth0)
idx=15
;;
ifcfg-eth1)
idx=16
;;
*)
printf "not found: "$file"\n"
continue
esac
target=${cfile[$idx]}
if [[ -e $target ]]; then
dtm=$(date +%Y-%m-%d)
mv $target $target"."$dtm
fi
source=$USERDIR"/"$file
dos2unix -q $source
rsync -q $source $target
if [ $? -eq 0 ]; then
sleep 0.1
printf "# "$target"\n"
fi
done
read -p "reboot now? (y|n)" selection
case $selection in
[Yy]*)
`reboot`
;;
*)
exit
;;
esac
fi
exit 0
Instead of
$target="/etc/httpd/conf/httpd.conf"
Use:
target="/etc/httpd/conf/httpd.conf"
When bash sees the former, it first substitutes in for "$target". If target was empty, then the line that bash tries to execute, after the variable substitution and quote removal steps, is:
=/etc/httpd/conf/httpd.conf
Since there is no file named "=/etc/httpd/conf/httpd.conf", bash returns with a "No such file or directory" error.
I have couple of files needed to be parsed here, called parse.sh. I have to enable optional arguments for it with -l for line, -f for field. So to run the program would be ./parse.sh -l 5 -f 14 foo. Without -l or -f arguments, I want the program to default parse all lines and all fields. If -l is specified, I want it to parse just that line of foo, and further if -f is specified too, I want it to parse just that field. I see getopts usually work like this:
while getopts "l:f:" opts; do
case $opts in
l) #code to parse that line;;
f) #code to parse that field;;
case
done
BUT this is not what I need, because I want -l and -f work together sometimes. I am thinking maybe I should do getopts to parse all options into array, then write code based on parsing that array? Any better choices?
This is my code:
while getopts "l:f:" opt;
do
options=${opt}${options}
case $opt in
l) lineNumber=$OPTARG ;;
f) fieldNumber=$OPTARG ;;
esac
done
case $options in
f) echo "Parse field $fieldNumber of each line" ;;
l) echo "Parse all fields of line number $lineNumber" ;;
lf | fl) echo "Parse field $fieldNumber of line $lineNumber" ;;
*) echo "Parse all fields of all lines" ;;
esac
#!/bin/bash
parse()
{
local lines=$1
local fields=$2
local file=$3
# logic goes here
echo "parsing line(s) ${lines} and field(s) ${fields} of file ${file}"
}
lines=all
fields=all
while getopts "l:f:" o; do
case $o in
l) lines=${OPTARG} ;;
f) fields=${OPTARG} ;;
esac
done
shift $((OPTIND-1))
for file; do
parse "${lines}" "${fields}" "${file}"
done
Example runs:
$ ./t.sh foo.txt bar.txt
parsing line(s) all and field(s) all of file foo.txt
parsing line(s) all and field(s) all of file bar.txt
$ ./t.sh -l 10 foo.txt bar.txt
parsing line(s) 10 and field(s) all of file foo.txt
parsing line(s) 10 and field(s) all of file bar.txt
$ ./t.sh -l 10 -f 5 foo.txt bar.txt
parsing line(s) 10 and field(s) 5 of file foo.txt
parsing line(s) 10 and field(s) 5 of file bar.txt
$ ./t.sh -f 5 foo.txt bar.txt
parsing line(s) all and field(s) 5 of file foo.txt
parsing line(s) all and field(s) 5 of file bar.txt
#!/bin/bash
while getopts "l:f:" opts; do
case $opts in
l)
lOn=1
;;
f)
fOn=1
;;
esac
done
if [[ -n $lOn && -n $fOn ]]; then
echo 'both l and f'
elif [[ -n $lOn ]]; then
echo 'just l'
elif [[ -n $fOn ]]; then
echo 'just f'
else
echo 'nothing'
fi
If statements give you more flexibility in situations when you want to check for other variables or do more complex stuff. This wont work in sh unless you change [[ ]] to [ ].
I made a concept script. Please try.
#!/bin/bash
PARSE_SPECIFIC_LINE=0
PARSE_SPECIFIC_FIELD=0
shopt -s extglob
while getopts "l:f:" opts; do
case $opts in
l)
if [[ $OPTARG != +([[:digit:]]) || OPTARG -lt 1 ]]; then
echo "Invalid argument to -l: $OPTARG"
exit 1
fi
PARSE_SPECIFIC_LINE=$OPTARG
;;
f)
if [[ $OPTARG != +([[:digit:]]) || OPTARG -lt 1 ]]; then
echo "Invalid argument to -f: $OPTARG"
exit 1
fi
PARSE_SPECIFIC_FIELD=$OPTARG
;;
esac
done
FILES=("${#:OPTIND}")
function parse_line {
local LINE=$1
if [[ -n $LINE ]]; then
if [[ PARSE_SPECIFIC_FIELD -gt 0 ]]; then
read -ra FIELDS <<< "$LINE"
echo "${FIELDS[PARSE_SPECIFIC_FIELD - 1]}"
else
echo "$LINE"
fi
fi
}
for F in "${FILES[#]}"; do
if [[ -e $F ]]; then
if [[ PARSE_SPECIFIC_LINE -gt 0 ]]; then
parse_line "$(sed -n "${PARSE_SPECIFIC_LINE}{p;q}" "$F")"
else
while read -r LINE; do
parse_line "$LINE"
done < "$F"
fi
else
echo "File does not exist: $F"
fi
done
I ran it with
bash script.sh -f 2 <(for i in {1..20}; do echo "$RANDOM $RANDOM $RANDOM $RANDOM $RANDOM"; done)
And I got
1031
1072
4350
12471
31129
32318
...
Adding -l 5 I got
11604