After moving from ubuntu to mac one of the commands I've been unable to live without is rgrep. I know it's possible to use grep -r 'term' * but is there an alias (or more likely a function) that can accomplish this without the flag and *?
How about?
rgrep() {
if [ "$#" -eq 0 ]; then
echo "Usage: $FUNCNAME pattern [options] -- see grep usage"
return
# one arg - pattern
elif [ "$#" -eq 1 ]; then
grep -rn "$#" *;
# 2 args - flag pattern
elif [ "$#" -eq 2 ]; then
first=$1
shift
grep -rn "$first" "$#" *;
# more than 2 args
else
echo "Usage: $FUNCNAME 2+ params not yet supported"
return
fi
}
Put this into ~/.bashrc:
alias rgrep='grep -r'
Then start a new shell to see the alias working.
Related
I'm facing a problem while trying to make an emacs daemon management function in Bash.
Here is the function snippet:
function ne
{
if [ $# -ge 2 -a "$1" '==' "-s" ]
then
server="$2";
param=${#:3};
else
server="default";
param=${#:1};
fi
nbsrv=`ls ~/.emacs.d/server | grep "$server" | wc --chars`
if [ "$nbsrv" '==' "0" ]
then
echo "Starting server '$server'";
emacs --daemon=$server
fi
emacsclient --server-file=$server -nw $param;
}
It almost works, the problem is with:
param=${#:x}
For exemple, if I run:
ne -s srv1 file1 file2
It does not open me 2 new files but one named "file1 file2"
Have you got an idea of how I can make this works fine?
Thank's !
JM445
PS: Sorry if my english is not perfect, I'm french
Don't bother with bash arrays for this. Just shift off the positional parameters you don't want, and pass the remainder to emacsclient with "$#"
Your script with this modification looks like:
if [ $# -ge 2 -a "$1" '==' "-s" ]
then
server="$2";
shift 2;
else
server="default";
fi
nbsrv=`ls ~/.emacs.d/server | grep "$server" | wc --chars`
if [ "$nbsrv" '==' "0" ]
then
echo "Starting server '$server'";
emacs --daemon=$server
fi
emacsclient --server-file=$server -nw "$#";
I was trying to combine logical AND & OR in a bash script within if condition. Somehow I am not getting the desired output and it is hard to troubleshoot.
I am trying to validate the input parameters passed to a shell script for no parameter and the first parameter passed is valid or not.
if [ "$#" -ne 1 ] && ([ "$1" == "ABC" ] || [ "$1" == "DEF" ] || [ "$1" == "GHI" ] || [ "$1" == "JKL" ])
then
echo "Usage: ./myscript.sh [ABC | DEF | GHI | JKL]"
exit 1
fi
Can anyone point out what is going wrong here?
The immediate problem with your statement is one of logic: you probably meant to write:
if [ "$#" -ne 1 ] || ! ([ "$1" = "ABC" ] || [ "$1" = "DEF" ] || [ "$1" = "GHI" ] || [ "$1" = "JKL" ])
then
echo "Usage: ./myscript.sh [ABC | DEF | GHI | JKL]" >&2
exit 1
fi
That is: abort, if either more than 1 argument is given OR if the single argument given does NOT equal one of the acceptable values.
Note the ! to negate the expression in parentheses and the use of the POSIX-compliant form of the string equality operator, = (rather than ==).
However, given that you're using Bash, you can make do with a single [[ ... ]] conditional and Bash's regular-expression matching operator, =~:
if [[ $# -ne 1 || ! $1 =~ ^(ABC|DEF|GHI|JKL)$ ]]
then
echo "Usage: ./myscript.sh [ABC | DEF | GHI | JKL]" >&2
exit 1
fi
If POSIX compliance is not required, [[ ... ]] is preferable to [ ... ] for a variety of reasons.
In the case at hand, $# and $1 didn't need quoting, and || could be used inside the conditional.
Note that =~ as used above works in Bash 3.2+, whereas the implicit extglob syntax used in anubhava's helpful answer requires Bash 4.1+;
in earlier versions you can, however, explicitly enable (and restore to its original value after) the extglob shell option: shopt -s extglob.
BASH actually allows use of extended glob inside [[ ... ]] and have && inside as well.
So you can do:
if [[ $# -ne 1 && $1 == #(ABC|DEF|GHI|JKL) ]]; then
echo "Usage: ./myscript.sh [ABC | DEF | GHI | JKL]"
exit 1
fi
A few things:
[...] in bash is equivalent to the same test command (check the man page), so those && and || are not logical operators, but rather the shell equivalents
Parentheses in POSIX shell are not a grouping operator. They will work here, but they open a subshell, you are better off using standard test options of -a and -o (making your if statement if [ "$#" -ne 1 -a \( "$1" == "ABC" -o "$1" == "DEF" -o "$1" == "GHI" -o "$1" == "JKL" \) ], though based on your logic, it sounds like you actually want something like if [ "$#" -ne 1 -o \( "$1" != "ABC" -a "$1" != "DEF" -a "$1" != "GHI" -a "$1" != "JKL" \) ]. You probably can get better results with a case statement like follows:
usage() {
echo "Usage: ./myscript.sh [ABC | DEF | GHI | JKL]"
}
if [ "$#" -ne 1 ]
then
usage
exit 1
fi
case "$1" in
ABC)
echo "Found ABC"
;;
DEF)
echo "Found DEF"
;;
GHI)
echo "Found GHI"
;;
JKL)
echo "Found JKL"
;;
*)
usage
exit 1
;;
esac
If you want to pass a set of possible static arguments in, you might want to look at the getopts special shell command.
I am writing a ksh function (that is placed in the .profile file) that will present a menu of subdirectories and permit the user to choose one into which to cd. Here is the code:
# Menu driven subdirectory descent.
d(){
# Only one command line argument accepted
[ "$1" = "--" ] && shift $# # Trap for "ls --" feature
wd=`pwd`; arg="${1:-$wd}"
dirs="`/bin/ls -AF $arg 2>/dev/null | grep /$ | tr -d \"/\"`"
# Set the names of the subdirectories to positional parameters
if [ "$dirs" ] ;then
set $dirs
if [ $# -eq 1 -a "$arg" = "$wd" ] ;then cd $arg/$1; return; fi # trap: it's obvious; do it
else echo "No subdirectories found" >&2; return 1
fi
# Format and display the menu
if [ `basename "${arg}X"` = "${arg}X" ] ;then arg="$wd/$arg"; fi # Force absolute path if relitive
echo -e "\n\t\tSubdirectories relative to ${arg}: \n"
j=1; for i; do echo -e "$j\t$i"; j=`expr $j + 1`; done | pr -r -t -4 -e3
echo -e "\n\t\tEnter the number of your choice -- \c "
# Convert user-input to directory-name and cd to it
read choice; echo
dir=`eval "(echo $\{"$choice"\})"` # Magic here.
[ "$choice" -a "$choice" -ge 1 -a "$choice" -le "$#" ] && cd $arg/`eval echo "$dir"`
}
This function works reasonably well with the exception of directory names that contain space characters. If the directory name contains a space, the set command sets each space delimited element of the directory name (instead of the complete directory name) into a separate positional parameter; that is not useful here.
I have attempted to set the $IFS shell variable (which contains a space, tab, and newline by default) to a single newline character with:
IFS=`echo` # echo outputs a trailing newline character by default
Which appears to accomplish what is intended as verified with:
echo -e "$IFS\c" | hexdump -c
But despite my best efforts (over the course of several days work) I have failed to set the entire directory names that contain spaces as values for positional parameters.
What am I missing?
Suggestions are hereby solicited and most welcome.
ADVAthanksNCE
Bob
Short answer: You can't do that. Don't try. See the ParsingLs page for an understanding of why programmatic use of ls is inherently error-prone.
You can't get -F behavior without implementing it yourself in shell (which is indeed feasible), but the following is the correct way to put a list of subdirectories into the argument list:
set -- */
If you don't want to have a literal / on the end of each entry:
set -- */ # put list of subdirectories into "$#"
set -- "${#%/}" # strip trailing / off each
Even better, though: Use an array to avoid needing eval magic later.
dirs=( */ )
dirs=( "${dirs[#]%/}" )
printf '%s\n' "${dirs[$choice]}" # emit entry at position $choice
Let's tie this all together:
d() {
destdir=$(
FIGNORE= # ksh93 equivalent to bash shopt -s dotglob
while :; do
subdirs=( ~(N)*/ ) # ksh93 equivalent to subdirs=( */ ) with shopt -s nullglob
(( ${#subdirs[#]} > 2 )) || break # . and .. are two entries
for idx in "${!subdirs[#]}"; do
printf '%d) %q\n' "$idx" "${subdirs[$idx]%/}" >&2
done
printf '\nSelect a subdirectory: ' >&2
read -r choice
if [[ $choice ]]; then
cd -- "${subdirs[$choice]}" || break
else
break
fi
done
printf '%s\n' "$PWD"
)
[[ $destdir ]] && cd -- "$destdir"
}
Although still not working, this version does pass shellcheck albeit with one exception:
3 # Menu driven subdirectory descent.
4 function d{
5 # Only one command line argument accepted
6 [ "$1" = "--" ] && shift $# # Trap for "ls --" feature
7 wd="$PWD"; arg="${1:-$wd}"
8 set -- "${#%/}" # Set the names of the subdirectories to positional parameters
9 if [ $# -eq 1 -a "$arg" = "$wd" ] ;then cd "$arg/$1" || exit 1; return; # trap: it's obvious; do it
10 else echo "No subdirectories found" >&2; return 1
11 fi
12 # Format and display the menu
13 if [[ $(basename "${arg}X") = "${arg}X" ]] ;then arg="$wd/${arg}"; fi # Force absolute path if relitive
14 echo -e "\n\t\tSubdirectories relative to ${arg}: \n"
15 j=1; for i; do echo -e "$j\t$i"; j=(expr $j + 1); done | pr -r -t -4 -e3
16 echo -e "\n\t\tEnter the number of your choice -- \c "
17 # Convert user-input to directory-name and cd to it
18 read -r choice; echo
19 dir=(eval "(echo $\{\"$choice\"\})") # Magic here.
20 [ "$choice" -a "$choice" -ge 1 -a "$choice" -le "$#" ] && cd "${arg}"/"$(eval echo "${dir}")" || exit 1
^SC2128 Expanding an array without an index only gives the first element.
21 }
Once I have incorporated your suggestions into the code, and made it functional, I'll post it here, and mark my question answered. Thank you for your kind assistance.
I've used the code you kindly wrote as a basis for the d function below. It pretty much does what I'd like, with a few little issues:
All subdirectory names that contain a SPACE character are surrounded by characters, but those that do not are not.
All subdirectory names that contain a SINGLE QUOTE character have that character escaped with a BACKSLASH character.
Given that 1 and 2 above cause no issues, they are acceptable, but not ideal.
After user input does the cd, the menu of subdirectory names is again looped through. This could be considered a feature, I suppose. I tried substituting a return for the brake commands in the sections of code following the cd commands, but was unsuccessful in overcoming the subsequent looped menu.
The inclusion of "." and ".." at the head of the menu of subdirectories is not ideal, and actually serves no good purpose.
------------------- Code Begins ------------------------------
d() {
if [ "$BASH" ] && [ "$BASH" != "/bin/sh" ]; then
echo "$FUNCNAME: ksh only";return 1
fi
FIGNORE= # ksh93 equivalent to bash shopt -s dotglob
if [ ${#} -gt 0 ] ;then # Only one command line argument accepted
cd -- "$1" && return 0
fi
if [ `ls -AF1|grep /|wc -l` -eq 1 ] ;then # cd if only one subdirectory
cd -- `ls -AF1|grep /` && return 0
fi
destdir=$(
while :; do
subdirs=( ~(N)*/ ) # ksh93 equivalent to subdirs=( */ ) with shopt -s nullglob
(( ${#subdirs[#]} > 2 )) || break # . and .. are two entries
echo -e "\n\t\tSubdirectories below ${PWD}: \n" >&2
for idx in "${!subdirs[#]}"; do
printf '%d) %q\n' "$idx" "${subdirs[$idx]%/}" >&2
done
printf '\nSelect a subdirectory: ' >&2
read -r
if [[ $REPLY ]]; then
cd -- "${subdirs[$REPLY]}" || break # Continue to loop through subdirectories after cding
else
break
fi
done
printf '%s\n' "$PWD"
)
--------------------------- Code Ends ------------------------------------
So, overall I'm very pleased, and consider myself very fortunate to have received the knowledgeable assistance of such an accomplished Unix wizard. I can't thank you enough.
# define some variables for later
date=`date`
usr=`whoami`
# define usage function to echo syntax if no args given
usage(){
echo "error: filename not specified"
echo "Usage: $0 filename directory/ directory/ directory/"
exit 1
}
# define copyall function
copyall() {
# local variable to take the first argument as file
local file="$1" dir
# shift to the next argument(s)
shift
# loop through the next argument(s) and copy $file to them
for dir in "$#"; do
cp -R "$file" "$dir"
done
}
# function to check if filename exists
# $f -> store argument passed to the script
file_exists(){
local f="$1"
[[ -f "$f" ]] && return 0 || return 1
}
# call usage() function to print out syntax
[[ $# -eq 0 ]] && usage
here's what I can't figure out
# call file_exists() and copyall() to do the dirty work
if ( file_exists "$1" ) then
copyall
I would also love to figure out how to take this next echo section and condense it to one line. Instead of $1 then shift then move on. Maybe split it into an array?
echo "copyall: File $1 was copied to"
shift
echo "$# on $date by $usr"
else
echo "Filename not found"
fi
exit 0
It seems to me that the file_exists macro is superfluous:
if [ -f "$1" ]
then copy_all "$#"
else echo "$0: no such file as $1" >&2; exit 1
fi
or even just:
[ -f "$1" ] && copy_all "$#"
You probably just need to remove the parentheses around file_exists "$1", and add a semicolon:
if file_exists "$1"; then
copyall
After several days of research, I still can't figure out the best method for parsing cmdline args in a .sh script. According to my references the getopts cmd is the way to go since it "extracts and checks switches without disturbing the positional parameter variables.Unexpected switches, or switches that are missing arguments, are recognized and reportedas errors."
Positional params(Ex. 2 - $#, $#, etc) apparently don't work well when spaces are involved but can recognize regular and long parameters(-p and --longparam). I noticed that both methods fail when passing parameters with nested quotes ("this is an Ex. of ""quotes""."). Which one of these three code samples best illustrates the way to deal with cmdline args? The getopt function is not recommended by gurus, so I'm trying to avoid it!
Example 1:
#!/bin/bash
for i in "$#"
do
case $i in
-p=*|--prefix=*)
PREFIX=`echo $i | sed 's/[-a-zA-Z0-9]*=//'`
;;
-s=*|--searchpath=*)
SEARCHPATH=`echo $i | sed 's/[-a-zA-Z0-9]*=//'`
;;
-l=*|--lib=*)
DIR=`echo $i | sed 's/[-a-zA-Z0-9]*=//'`
;;
--default)
DEFAULT=YES
;;
*)
# unknown option
;;
esac
done
exit 0
Example 2:
#!/bin/bash
echo ‘number of arguments’
echo "\$#: $#"
echo ”
echo ‘using $num’
echo "\$0: $0"
if [ $# -ge 1 ];then echo "\$1: $1"; fi
if [ $# -ge 2 ];then echo "\$2: $2"; fi
if [ $# -ge 3 ];then echo "\$3: $3"; fi
if [ $# -ge 4 ];then echo "\$4: $4"; fi
if [ $# -ge 5 ];then echo "\$5: $5"; fi
echo ”
echo ‘using $#’
let i=1
for x in $#; do
echo "$i: $x"
let i=$i+1
done
echo ”
echo ‘using $*’
let i=1
for x in $*; do
echo "$i: $x"
let i=$i+1
done
echo ”
let i=1
echo ‘using shift’
while [ $# -gt 0 ]
do
echo "$i: $1"
let i=$i+1
shift
done
[/bash]
output:
bash> commandLineArguments.bash
number of arguments
$#: 0
using $num
$0: ./commandLineArguments.bash
using $#
using $*
using shift
#bash> commandLineArguments.bash "abc def" g h i j*
Example 3:
#!/bin/bash
while getopts ":a:" opt; do
case $opt in
a)
echo "-a was triggered, Parameter: $OPTARG" >&2
;;
\?)
echo "Invalid option: -$OPTARG" >&2
exit 1
;;
:)
echo "Option -$OPTARG requires an argument." >&2
exit 1
;;
esac
done
exit 0
I find the use of getopt to be the easiest. It provides correct handling of arguments which is tricky otherwise. For example, getopt will know how to handle arguments to a long option specified on the command line as --arg=option or --arg option.
What is useful in parsing any input passed to a shell script is the use of the "$#" variables. See the bash man page for how this differs from $#. It ensures that you can process arguments that include spaces.
Here's an example of how I might write s script to parse some simple command line arguments:
#!/bin/bash
args=$(getopt -l "searchpath:" -o "s:h" -- "$#")
eval set -- "$args"
while [ $# -ge 1 ]; do
case "$1" in
--)
# No more options left.
shift
break
;;
-s|--searchpath)
searchpath="$2"
shift
;;
-h)
echo "Display some help"
exit 0
;;
esac
shift
done
echo "searchpath: $searchpath"
echo "remaining args: $*"
And used like this to show that spaces and quotes are preserved:
user#machine:~/bin$ ./getopt_test --searchpath "File with spaces and \"quotes\"."
searchpath: File with spaces and "quotes".
remaining args: other args
Some basic information about the use of getopt can be found here
If you want to avoid using getopt you can use this nice quick approach:
Defining help with all options as ## comments (customise as you wish).
Define for each option a function with same name.
Copy the last five lines of this script to your script (the magic).
Example script: log.sh
#!/bin/sh
## $PROG 1.0 - Print logs [2017-10-01]
## Compatible with bash and dash/POSIX
##
## Usage: $PROG [OPTION...] [COMMAND]...
## Options:
## -i, --log-info Set log level to info (default)
## -q, --log-quiet Set log level to quiet
## -l, --log MESSAGE Log a message
## Commands:
## -h, --help Displays this help and exists
## -v, --version Displays output version and exists
## Examples:
## $PROG -i myscrip-simple.sh > myscript-full.sh
## $PROG -r myscrip-full.sh > myscript-simple.sh
PROG=${0##*/}
LOG=info
die() { echo $# >&2; exit 2; }
log_info() {
LOG=info
}
log_quiet() {
LOG=quiet
}
log() {
[ $LOG = info ] && echo "$1"; return 1 ## number of args used
}
help() {
grep "^##" "$0" | sed -e "s/^...//" -e "s/\$PROG/$PROG/g"; exit 0
}
version() {
help | head -1
}
[ $# = 0 ] && help
while [ $# -gt 0 ]; do
CMD=$(grep -m 1 -Po "^## *$1, --\K[^= ]*|^##.* --\K${1#--}(?:[= ])" log.sh | sed -e "s/-/_/g")
if [ -z "$CMD" ]; then echo "ERROR: Command '$1' not supported"; exit 1; fi
shift; eval "$CMD" $# || shift $? 2> /dev/null
done
Testing
Running this command:
./log.sh --log yep --log-quiet -l nop -i -l yes
Produces this output:
yep
yes
By the way: It's compatible with posix!