I am trying to have an alias with if-then-else condition
on a command result.
the alias will get a file pattern
if there is just one file it will open the file
if there is more or less then 1 files - it will indicate with a message.
the alias I have tried is:
alias atest 'if \("ls \!:1" > 0 \) "nedit ls \!:1" else "echo cant open the file" '
There are a couple of ways, most of them are not very elegant, but the following is the best looking and easiest to create that I have discovered:
alias aliasname '`if (condition == check) echo "echo"` >&/dev/null && code_if_true || code_if_false'
You have the ability to nest if statements following this format, and can also use it as a checker for arguments if you change the interior of the if statement to be
if ("\!:1" =~ "check")
The >&/dev/null is to clean up the output of the function, but is not necessary. If you are interested, there is also a way to make for statements within aliases, but that method is a lot less elegant. I haven't used it but it is necessary if you wish to create an alias with a varying number of arguments. Again, that one is ugly, so at that point I'd just write a function.
You can't do this with an alias. An alias can only expand into a one-line command, but if requires multiple lines.
Instead, use a shell script. The script doesn't have to be in the same language you use interactively, so you can use POSIX shell, which is generally considered much better for programming than C shell (see Csh Programming Considered Harmful).
#!/bin/sh
if [ $# -eq 1 ]
then nedit "$1"
else
echo "Can't open the file"
exit 1
fi
Put this in a file named atest, give it execute permissions, and put it in a directory that's in your $PATH.
Building on Jonathan Roberts solution. This is part of a bash script on the local server. Checks for user XXXXX if true, sends through ssh the BASH command. Otherwise sends the TCSH command. The command checks to see if a directory exists returns true or false
if [[ ${LOCAL_USER} == "XXXXX" ]]; then
LOCAL_DIR_EXIST_CHECK=$(ssh -v -q -i ~/.ssh/"${LOCAL_SSH_KEY_FILE}" "${LOCAL_USER}#${LOCAL_SERVER}" "if [[ -d ${LOCAL_CLIENT_DIRECTORY} ]];then echo 'TRUE'; else echo 'FALSE'; fi")
else
LOCAL_DIR_EXIST_CHECK=$(ssh -v -q -i ~/.ssh/"${LOCAL_SSH_KEY_FILE}" "${LOCAL_USER}#${LOCAL_SERVER}" '`if ( -d ' ${LOCAL_CLIENT_DIRECTORY} ') echo "echo"` > & /dev/null && echo TRUE || echo FALSE')
fi
Actually, tcsh can have aliases with multiple lines in them. Just finish each line with backslash () and continue writing the alias on a next line.
~> alias atest 'set __atest_pattern="\!:1" \
? set __atest_files=( "`ls -d $__atest_pattern`" ) \
? if ( $#__atest_files == 1 ) then \
? nedit "$__atest_files[1]" \
? else if ( $#__atest_files > 1 ) then \
? echo "Too many files ($#__atest_files) are matched to '\''$__atest_pattern'\''" \
? endif'
~> alias atest
set __atest_pattern="!:1"
set __atest_files=( "`ls -d $__atest_pattern`" )
if ( $#__atest_files == 1 ) then
nedit "$__atest_files[1]"
else if ( $#__atest_files > 1 ) then
echo "Too many files ($#__atest_files) are matched to '$__atest_pattern'"
endif
~> atest *
Too many files (73) are matched to '*'
~> atest dummy
ls: cannot access dummy: No such file or directory
~> atest /dummy/*
ls: No match.
~> atest .cshrc
# Actually nedit was invoked here
~>
Using #JonathanRoberts answer I was able to finally improve an alias for exit that I use in shells inside screen so that I don't accidentally exit when I really want to detach. So, now I can override the faked exit with exit now if I want and really exit:
In ~/.tcshrc
if ($?TERM && $TERM =~ screen.*) then
#OLD ALIAS# alias exit "echo 'exit disabled (via alias)'; screen -d"
alias exit '`if ("\!:$" == "now") echo "echo"` >&/dev/null && exec false || echo "really? use: exit now" ; screen -d'
endif
Related
#!/bin/bash
echo 'Please enter the name of the species you are looking for: '
read speciesName
grep "$speciesName" speciesDetails.txt | awk '{print $0}'
echo
echo 'Would you like to search for another species? Press y to search or n to go back
to the main menu: '
read answer
case $answer in
[yY] | [yY][eE][sS] )
./searchSpecies.sh;;
[nN] | [nN][oO] )
./speciesMenu.sh;;
*) echo exit;;
esac
If there is no entry of that species name in the file how do I give the user an error to say not found?
The answer to your immediate question is to examine the exit code from grep. But probably also refactor the loop:
#!/bin/bash
while true; do
read -p 'Please enter the name of the species you are looking for: ' -r speciesName
grep -e "$speciesName" speciesDetails.txt || echo "$speciesName: not found" >&2
read -p 'Would you like to search for another species? Press n to quit: ' -r answer
case $answer in
[nN] | [nN][oO] )
break;;
esac
done
A better design altogether is probably to make the search term a command-line argument. This makes the script easier to use from other scripts, and the user can use the shell's facilities for history, completion, etc to run it as many times as they like, and easily fix e.g. typos by recalling the previous invocation and editing it.
#!/bin/bash
grep -e "$1" speciesDetails.txt || echo "$1: not found" >&2
The short-circuit one || two corresponds to the longhand
if one; then
: nothing
else
two
fi
If you want to search for static strings, not regular expressions, maybe add -F to the grep options.
If you need just check existance then execute it using next way :
if grep speciesName4 speciesDetails.txt; then
echo "exist";
else
echo "Not exist";
fi
Use $? to check exit code of the command if you need return value as well
set -o pipefail
$ echo "speciesName1" > speciesDetails.txt
$ echo "speciesName2" >> speciesDetails.txt
$ echo "speciesName3" >> speciesDetails.txt
$ l_result=$(grep speciesName3 speciesDetails.txt); l_exit_code=$?
$ echo $l_exit_code
0
$ l_result=$(grep speciesName4 speciesDetails.txt); l_exit_code=$?
$ echo $l_exit_code
1
Updated:
It is not antipattern if you need to use later output of your command
I've been trying to create a bash script that can move up a directory. I created this script, but when ran it does not execute anything. I tried adding a print statement to it, and that does work. Is there a certain way I should be executing this?
Script:
#!/usr/bin/zsh
DIR=$1
NUM=$PWD
for ((c=1; c <= DIR; c++))
do
echo $NUM
cd $NUM/..
done
If I understand, you like to move a directory up in the directory tree.
This is a script make aliases in .bashrc Like: alias up1='cd../' alias up2='cd../../' and so on, I limit the depth 9. If you Run it multiple times the script only crate the not existing entrys. May I will able to make it delete entry if you give less depth then script would manage this alias.
Not exactly you looking for but since script running in they own instance you cannot make them affect your current shell. Also after this script create aliases you need re authenticate or open a new shell, from that point they will work till you not delete the alias entry from .bashrc.
#!/bin/sh
[[ ! $# == 1 ]] && echo "Only one parameter accepted" && exit 1
[[ $( echo $1 | grep -c ^[1-9]$ ) -eq 0 ]] && echo "parameter must be between 1 and 9" && exit 1
cdcommand=""
for (( i = 1 ; i <= $1 ; i++ )); do
cdcommand=$(echo $cdcommand | sed 's/^/\.\.\//g')
[[ $( cat ~/.bashrc | grep -c "alias up$i='cd $cdcommand' ") == 0 ]] &&
echo "alias up$i='cd $cdcommand' " >> ~/.bashrc
done
I am looking into how a particular exploit works, and I chose to look at one in the program 'chkrootkit' which allows for any user to run a malicious file as root. The source code for this vulnerable shellscript is as follows
slapper (){
SLAPPER_FILES="${ROOTDIR}tmp/.bugtraq ${ROOTDIR}tmp/.bugtraq.c"
SLAPPER_FILES="$SLAPPER_FILES ${ROOTDIR}tmp/.unlock ${ROOTDIR}tmp/httpd \
${ROOTDIR}tmp/update ${ROOTDIR}tmp/.cinik ${ROOTDIR}tmp/.b"a
SLAPPER_PORT="0.0:2002 |0.0:4156 |0.0:1978 |0.0:1812 |0.0:2015 "
OPT=-an
STATUS=0
file_port=
if ${netstat} "${OPT}"|${egrep} "^tcp"|${egrep} "${SLAPPER_PORT}">
/dev/null 2>&1
then
STATUS=1
[ "$SYSTEM" = "Linux" ] && file_port=`netstat -p ${OPT} | \
$egrep ^tcp|$egrep "${SLAPPER_PORT}" | ${awk} '{ print $7 }' |
tr -d :`
fi
for i in ${SLAPPER_FILES}; do
if [ -f ${i} ]; then
file_port=$file_port $i
STATUS=1
fi
done
if [ ${STATUS} -eq 1 ] ;then
echo "Warning: Possible Slapper Worm installed ($file_port)"
else
if [ "${QUIET}" != "t" ]; then echo "not infected"; fi
return ${NOT_INFECTED}
fi
}
I know that the reason the exploit works is because the line 'file_port=$file_port $i' will execute all files specified in $SLAPPER_FILES as the user chkrootkit is running (usually root), if $file_port is empty, because of missing quotation marks around the
variable assignment."
My question is why does the command
file_port=$file_port $i
result in execution of the file? Assuming that $i refers to the path of the file (/tmp/update)
I can see that file_port might be changed to some long netstat command in the previous if statement, is this something to do with it?
I've been trying to get my head around this all day to no avail, so at this point any help will be greatly appreciated :)
This is the one-shot variable assignment feature of any Bourne shell. Any command can be prefixed with zero or more variable assignments:
VAR1=VALUE1 VAR2=VALUE2 command arguments ...
Runs command arguments ... with the respective environment variables set for just this command. A typical use might be
EDITOR=vim crontab -e
How to check if two variables in a shell script point to the same folder?
It can't be checked just by comparing these variables:
d1=/home/
d2=/home/user/../
if [ "$d1" == "$d2" ]
then
echo true
else
echo false
fi
... outputs false
Let us assume: d1 and d2, both are directories paths on the file system and are pointing to the same directory. However they are written differently as /home/ and /home/user/../. Therefore simples checking for equal strings does not work here.
Inspired by the question:
A reliable way to check if two variables in a cmd shell point to the same folder
Within the answer:
for %%A in ("%d1%") do for %%B in ("%d2%") do if "%%~fA"=="%%~fB" (echo true) else (echo false)
var1="/home/ps/temp/.."
var2="/home/ps/"
if [ "$(readlink -f "$var1")" == "$(readlink -f "$var2")" ];then
echo "they are pointing to same directory...."
else
echo "NOOOOO they are different"
fi
From man readlink :
-f, --canonicalize
canonicalize by following every symlink in every component of the given name recursively; all but the last component must exist
Try using the stat command to report the inode number. Use a trailing backslash to insure any symlinks are dereferenced.
inode_1=`stat -f "%i" ../../d1/`
inode_2=`stat -f "%1" /path/to/d2/which/links/to/d1/`
[[ $inode_1 -eq $inode_2 ]] && echo "true" || echo "false"
I am trying to make a shell script which is designed to be run like this:
script.sh -t application
Firstly, in my script I want to check to see if the script has been run with the -t flag. For example if it has been run without the flag like this I want it to error:
script.sh
Secondly, assuming there is a -t flag, I want to grab the value and store it in a variable that I can use in my script for example like this:
FLAG="application"
So far the only progress I've been able to make on any of this is that $# grabs all the command line arguments but I don't know how this relates to flags, or if this is even possible.
You should read this getopts tutorial.
Example with -a switch that requires an argument :
#!/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
Like greybot said(getopt != getopts) :
The external command getopt(1) is never safe to use, unless you know
it is GNU getopt, you call it in a GNU-specific way, and you ensure
that GETOPT_COMPATIBLE is not in the environment. Use getopts (shell
builtin) instead, or simply loop over the positional parameters.
Use $# to grab the number of arguments, if it is unequal to 2 there are not enough arguments provided:
if [ $# -ne 2 ]; then
usage;
fi
Next, check if $1 equals -t, otherwise an unknown flag was used:
if [ "$1" != "-t" ]; then
usage;
fi
Finally store $2 in FLAG:
FLAG=$2
Note: usage() is some function showing the syntax. For example:
function usage {
cat << EOF
Usage: script.sh -t <application>
Performs some activity
EOF
exit 1
}
Here is a generalized simple command argument interface you can paste to the top of all your scripts.
#!/bin/bash
declare -A flags
declare -A booleans
args=()
while [ "$1" ];
do
arg=$1
if [ "${1:0:1}" == "-" ]
then
shift
rev=$(echo "$arg" | rev)
if [ -z "$1" ] || [ "${1:0:1}" == "-" ] || [ "${rev:0:1}" == ":" ]
then
bool=$(echo ${arg:1} | sed s/://g)
booleans[$bool]=true
echo \"$bool\" is boolean
else
value=$1
flags[${arg:1}]=$value
shift
echo \"$arg\" is flag with value \"$value\"
fi
else
args+=("$arg")
shift
echo \"$arg\" is an arg
fi
done
echo -e "\n"
echo booleans: ${booleans[#]}
echo flags: ${flags[#]}
echo args: ${args[#]}
echo -e "\nBoolean types:\n\tPrecedes Flag(pf): ${booleans[pf]}\n\tFinal Arg(f): ${booleans[f]}\n\tColon Terminated(Ct): ${booleans[Ct]}\n\tNot Mentioned(nm): ${boolean[nm]}"
echo -e "\nFlag: myFlag => ${flags["myFlag"]}"
echo -e "\nArgs: one: ${args[0]}, two: ${args[1]}, three: ${args[2]}"
By running the command:
bashScript.sh firstArg -pf -myFlag "my flag value" secondArg -Ct: thirdArg -f
The output will be this:
"firstArg" is an arg
"pf" is boolean
"-myFlag" is flag with value "my flag value"
"secondArg" is an arg
"Ct" is boolean
"thirdArg" is an arg
"f" is boolean
booleans: true true true
flags: my flag value
args: firstArg secondArg thirdArg
Boolean types:
Precedes Flag(pf): true
Final Arg(f): true
Colon Terminated(Ct): true
Not Mentioned(nm):
Flag: myFlag => my flag value
Args: one => firstArg, two => secondArg, three => thirdArg
Basically, the arguments are divided up into flags booleans and generic arguments.
By doing it this way a user can put the flags and booleans anywhere as long as he/she keeps the generic arguments (if there are any) in the specified order.
Allowing me and now you to never deal with bash argument parsing again!
You can view an updated script here
This has been enormously useful over the last year. It can now simulate scope by prefixing the variables with a scope parameter.
Just call the script like
replace() (
source $FUTIL_REL_DIR/commandParser.sh -scope ${FUNCNAME[0]} "$#"
echo ${replaceFlags[f]}
echo ${replaceBooleans[b]}
)
Doesn't look like I implemented argument scope, not sure why I guess I haven't needed it yet.
Try shFlags -- Advanced command-line flag library for Unix shell scripts.
https://github.com/kward/shflags
It is very good and very flexible.
FLAG TYPES: This is a list of the DEFINE_*'s that you can do. All flags take
a name, default value, help-string, and optional 'short' name (one-letter
name). Some flags have other arguments, which are described with the flag.
DEFINE_string: takes any input, and intreprets it as a string.
DEFINE_boolean: typically does not take any argument: say --myflag to set
FLAGS_myflag to true, or --nomyflag to set FLAGS_myflag to false.
Alternately, you can say
--myflag=true or --myflag=t or --myflag=0 or
--myflag=false or --myflag=f or --myflag=1
Passing an option has the same affect as passing the option once.
DEFINE_float: takes an input and intreprets it as a floating point number. As
shell does not support floats per-se, the input is merely validated as
being a valid floating point value.
DEFINE_integer: takes an input and intreprets it as an integer.
SPECIAL FLAGS: There are a few flags that have special meaning:
--help (or -?) prints a list of all the flags in a human-readable fashion
--flagfile=foo read flags from foo. (not implemented yet)
-- as in getopt(), terminates flag-processing
EXAMPLE USAGE:
-- begin hello.sh --
! /bin/sh
. ./shflags
DEFINE_string name 'world' "somebody's name" n
FLAGS "$#" || exit $?
eval set -- "${FLAGS_ARGV}"
echo "Hello, ${FLAGS_name}."
-- end hello.sh --
$ ./hello.sh -n Kate
Hello, Kate.
Note: I took this text from shflags documentation