"unary operator expected" in condition in Bash [duplicate] - linux

In my script I am trying to error check if the first and only argument is equal to -v, but it is an optional argument. I use an if statement, but I keep getting the unary operator expected error.
This is the code:
if [ $1 != -v ]; then
echo "usage: $0 [-v]"
exit
fi
To be more specific:
This part of the script above is checking an optional argument and then after, if the argument is not entered, it should run the rest of the program.
#!/bin/bash
if [ "$#" -gt "1" ]; then
echo "usage: $0 [-v]"
exit
fi
if [ "$1" != -v ]; then
echo "usage: $0 [-v]"
exit
fi
if [ "$1" = -v ]; then
echo "`ps -ef | grep -v '\['`"
else
echo "`ps -ef | grep '\[' | grep root`"
fi

Quotes!
if [ "$1" != -v ]; then
Otherwise, when $1 is completely empty, your test becomes:
[ != -v ]
instead of
[ "" != -v ]
...and != is not a unary operator (that is, one capable of taking only a single argument).

Or for what seems like rampant overkill, but is actually simplistic ... Pretty much covers all of your cases, and no empty string or unary concerns.
In the case the first arg is '-v', then do your conditional ps -ef, else in all other cases throw the usage.
#!/bin/sh
case $1 in
'-v') if [ "$1" = -v ]; then
echo "`ps -ef | grep -v '\['`"
else
echo "`ps -ef | grep '\[' | grep root`"
fi;;
*) echo "usage: $0 [-v]"
exit 1;; #It is good practice to throw a code, hence allowing $? check
esac
If one cares not where the '-v' arg is, then simply drop the case inside a loop. The would allow walking all the args and finding '-v' anywhere (provided it exists). This means command line argument order is not important. Be forewarned, as presented, the variable arg_match is set, thus it is merely a flag. It allows for multiple occurrences of the '-v' arg. One could ignore all other occurrences of '-v' easy enough.
#!/bin/sh
usage ()
{
echo "usage: $0 [-v]"
exit 1
}
unset arg_match
for arg in $*
do
case $arg in
'-v') if [ "$arg" = -v ]; then
echo "`ps -ef | grep -v '\['`"
else
echo "`ps -ef | grep '\[' | grep root`"
fi
arg_match=1;; # this is set, but could increment.
*) ;;
esac
done
if [ ! $arg_match ]
then
usage
fi
But, allow multiple occurrences of an argument is convenient to use in situations such as:
$ adduser -u:sam -s -f -u:bob -trace -verbose
We care not about the order of the arguments, and even allow multiple -u arguments. Yes, it is a simple matter to also allow:
$ adduser -u sam -s -f -u bob -trace -verbose

Related

LINUX script bash [duplicate]

I want to check if a file contains a specific string or not in bash. I used this script, but it doesn't work:
if [[ 'grep 'SomeString' $File' ]];then
# Some Actions
fi
What's wrong in my code?
if grep -q SomeString "$File"; then
Some Actions # SomeString was found
fi
You don't need [[ ]] here. Just run the command directly. Add -q option when you don't need the string displayed when it was found.
The grep command returns 0 or 1 in the exit code depending on
the result of search. 0 if something was found; 1 otherwise.
$ echo hello | grep hi ; echo $?
1
$ echo hello | grep he ; echo $?
hello
0
$ echo hello | grep -q he ; echo $?
0
You can specify commands as an condition of if. If the command returns 0 in its exitcode that means that the condition is true; otherwise false.
$ if /bin/true; then echo that is true; fi
that is true
$ if /bin/false; then echo that is true; fi
$
As you can see you run here the programs directly. No additional [] or [[]].
In case if you want to check whether file does not contain a specific string, you can do it as follows.
if ! grep -q SomeString "$File"; then
Some Actions # SomeString was not found
fi
In addition to other answers, which told you how to do what you wanted, I try to explain what was wrong (which is what you wanted.
In Bash, if is to be followed with a command. If the exit code of this command is equal to 0, then the then part is executed, else the else part if any is executed.
You can do that with any command as explained in other answers: if /bin/true; then ...; fi
[[ is an internal bash command dedicated to some tests, like file existence, variable comparisons. Similarly [ is an external command (it is located typically in /usr/bin/[) that performs roughly the same tests but needs ] as a final argument, which is why ] must be padded with a space on the left, which is not the case with ]].
Here you needn't [[ nor [.
Another thing is the way you quote things. In bash, there is only one case where pairs of quotes do nest, it is "$(command "argument")". But in 'grep 'SomeString' $File' you have only one word, because 'grep ' is a quoted unit, which is concatenated with SomeString and then again concatenated with ' $File'. The variable $File is not even replaced with its value because of the use of single quotes. The proper way to do that is grep 'SomeString' "$File".
Shortest (correct) version:
grep -q "something" file; [ $? -eq 0 ] && echo "yes" || echo "no"
can be also written as
grep -q "something" file; test $? -eq 0 && echo "yes" || echo "no"
but you dont need to explicitly test it in this case, so the same with:
grep -q "something" file && echo "yes" || echo "no"
##To check for a particular string in a file
cd PATH_TO_YOUR_DIRECTORY #Changing directory to your working directory
File=YOUR_FILENAME
if grep -q STRING_YOU_ARE_CHECKING_FOR "$File"; ##note the space after the string you are searching for
then
echo "Hooray!!It's available"
else
echo "Oops!!Not available"
fi
grep -q [PATTERN] [FILE] && echo $?
The exit status is 0 (true) if the pattern was found; otherwise blankstring.
if grep -q [string] [filename]
then
[whatever action]
fi
Example
if grep -q 'my cat is in a tree' /tmp/cat.txt
then
mkdir cat
fi
In case you want to checkif the string matches the whole line and if it is a fixed string, You can do it this way
grep -Fxq [String] [filePath]
example
searchString="Hello World"
file="./test.log"
if grep -Fxq "$searchString" $file
then
echo "String found in $file"
else
echo "String not found in $file"
fi
From the man file:
-F, --fixed-strings
Interpret PATTERN as a list of fixed strings, separated by newlines, any of
which is to be matched.
(-F is specified by POSIX.)
-x, --line-regexp
Select only those matches that exactly match the whole line. (-x is specified by
POSIX.)
-q, --quiet, --silent
Quiet; do not write anything to standard output. Exit immediately with zero
status if any match is
found, even if an error was detected. Also see the -s or --no-messages
option. (-q is specified by
POSIX.)
Try this:
if [[ $(grep "SomeString" $File) ]] ; then
echo "Found"
else
echo "Not Found"
fi
I done this, seems to work fine
if grep $SearchTerm $FileToSearch; then
echo "$SearchTerm found OK"
else
echo "$SearchTerm not found"
fi
grep -q "something" file
[[ !? -eq 0 ]] && echo "yes" || echo "no"

How can I add a third parameter to this bash script?

I want to add the third parameter that will be changing files name from upper to lower OR lower to upper but in this third parameter I want to specify what file's name must be changed? What's wrong with this script? Thank you in advance.
#!/bin/bash
if test "$1" = "lower" && test "$2" = "upper"
then
for file in *; do
if [ $0 != "$file" ] && [ $0 != "./$file" ]; then
mv "$file" "$(echo $file | tr [:lower:] [:upper:])";
fi
fi
done
elif test "$1" = "upper" && test "$2" = "lower"
then
for file in *; do
if [ $0 != "$file" ] && [ $0 != "./$file" ]; then
mv "$file" "$(echo $file | tr [:upper:] [:lower:])";
fi
done
fi
if [ "$1" = "lower" ] && [ "$2" = "upper" ] && [ "$3" = "$file" ];
then
for file in * ; do
if [ $0 != "$file" ] && [ $0 != "./$file" ]; then
mv "$file" "$(echo $file | tr [:lower:] [:upper:])";
fi
done
fi
If I am guessing correctly what you want, try
#!/bin/bash
case $1:$2 in
upper:lower | lower:upper ) ;;
*) echo "Syntax: $0 upper|lower lower|upper files ..." >&2; exit 1;;
esac
from=$1
to=$2
shift; shift
for file; do
mv "$file" "$(echo "$file" | tr "[:$from:]" "[:$to:]")"
done
This has the distinct advantage that it allows more than three arguments, where the first two specify the operation to perform.
Notice also how we take care to always quote strings which contain a file name. See also When to wrap quotes around a shell variable?
The above script should in fact also work with /bin/sh; we do not use any Bash-only features so it should run under any POSIX sh.
However, a much better design would probably be to use an option to decide what mapping to apply, and simply accept a (possibly empty) list of options and a list of file name arguments. Then you can use Bash built-in parameter expansion, too. Case conversion parameter expansion operations are available in Bash 4 only, though.
#!/bin/bash
op=',,'
# XXX FIXME: do proper option parsing
case $1 in -u) op='^^'; shift;; esac
for file; do
eval mv "\$file" "\${file$op}"
done
This converts to lowercase by default, and switches to uppercase instead if you pass in -u before the file names.
In both of these scripts, we use for file as a shorthand for for file in "$#" i.e. we loop over the (remaining) command-line arguments. Perhaps this is the detail you were looking for.
Forgive me if I grossly misunderstand, but I think you may have misunderstood how argument passing works.
The named/numbered arguments represent the values you pass in on the command line in their ordinal positions. Each can theoretically have any value that can by stuck in a string. You don't need a third parameter, just a third value.
Let's try a sample.
#! /bin/env bash
me=${0#*/} # strip the path
use="
$me { upper | lower } file
changes the NAME of the file given to be all upper or all lower case.
"
# check for correct arguments
case $# in
2) : exactly 2 arguments passed - this is correct ;;
*) echo "Incorrect usage - $me requires exactly 2 arguments $use" >&2
exit 1 ;;
esac
declare -l lower action # these variables will downcase anything put in them
declare -u upper # this one will upcase anything in it
declare newname # create a target variable with unspecified case
action="$1" # stored the *lowercased* 1st argument passed as $action
case $action in # passed argument has been lowercased for simpler checking
upper) upper="$2" # store *uppercased* 2nd arg.
newname="$upper" # newname is now uppercase.
;;
lower) lower="$2" # store *lowercased* 2nd arg.
newname="$lower" # newname is now lowercase.
;;
*) echo "Incorrect usage - $me requires 2nd arg to be 'upper' or 'lower' $use" >&2
exit 1 ;;
esac
if [[ -e "$2" ]] # confirm the argument exists
then echo "Renaming $2 -> $newname:"
ls -l "$2"
echo " -> "
mv "$2" "$newname" # rename the file
ls -l "$newname"
else echo "'$2' does not exist. $use" >&2
exit 1
fi
First of all there is indentation problem with this script check first if condition done should be coming before fi
Below is the correct.
if test "$1" = "lower" && test "$2" = "upper"
then
for file in *; do
if [ $0 != "$file" ] && [ $0 != "./$file" ]; then
mv "$file" "$(echo $file | tr [:lower:] [:upper:])";
fi
done
fi
Secondly the question you asked:
#/bin/bash -xe
[ $# -ne 3 ] && echo "Usage: {lower} {upper} {fileName} " && exit 1
if [ "$1" = "lower" ] && [ "$2" = "upper" ] && [ -f "$3" ];
then
mv "$3" "$(echo $3 | tr [:lower:] [:upper:])";
fi
Hope this helps.

how to do if statements with "and" in sh

I try to solve this problem with the sh and not the bash.
All i want is a if statement that check some regex AND something else. Normally in bash this would be an easy job but with the sh i only find solutions online that wont work for me
First thing i want to check:
if echo "$1"| grep -E -q '^(\-t|\-\-test)$';
Than i want to check:
if echo "$#"| grep -E -q '^(1|2)$';
Combined:
if [ \(echo "$1"| grep -E -q '^(\-h|\-\-help)$'\) -a \(echo "$#"| grep -E -q '^(1|\2)$'\) ];
ERROR:
grep: grep: ./analysehtml.sh: 41: [: missing ]
Invalid back reference
(echo: No such file or directory
grep: 1: No such file or directory
I also try many diffrent combinations with this brackets but non of them worked for me. Maybe someone can help me here :)
logical and between commands is &&
if
echo "$1"| grep -E -q '^(\-h|\-\-help)$' &&
echo "$#"| grep -E -q '^(1|\2)$';
By default the exit status of a pipe is the exit status of last command.
set -o pipefail the exit status is fail if if any command of pipe has a fail exit status.
when only the exit status of the last command of a sequence must be checked
if { command11; command12;} && { command21; command22;};
However to check parameters there is no need to launch another process grep with a pipe there's an overhead.
Consider using following constructs work with any POSIX sh.
if { [ "$1" = -h ] || [ "$1" = --help ];} &&
{ [ $# -eq 1 ] || [ $# -eq 2 ];};
EDIT: Following are not POSIX but may work with many shell
if [[ $1 = -h || $1 = --help ]] && [[ $# = 1 || $# = 2 ]];
Works also with bash with set -o posix
Perhaps for your particular case, pattern matching might be better:
if [[ $1 =~ ^(\-h|\-\-help)$ && $# =~ ^(1|\2)$ ]]; then
The problem with your command is that the part within test or [ command is expression, not commands list.
So when you run [ echo 'hello' ] or [ \( echo 'hello' \) ] complains error in spite of sh or Bash. Refer to the classic test usage: The classic test command
And the syntax of if is:
if list; then list; fi
So you can just combine command with && operator in if statements:
if echo "$1"| grep -E -q '^(\-h|\-\-help)$' && echo "$#"| grep -E -q '^(1|\2)$';

Bash:Else not working in if/else statement in case statement

I am trying to check if a user types multiple arguments in a command line using case and if/else statements. What's wrong is that I keep getting the default case instead of the same command, but with 2 more arguments. For instance, one of my conditions is:
del)
if [ -z "$2" ] || [ -z "$3" ]
then
echo "Usage: removes a file"
else
echo "using Bash command: rm $2 $3"
rm $2 $3
echo done
fi
prints the first condition, but if I type, say, del aaa bbb, I get the default case, which is:
echo "ERROR: Unrecognized command"
I'm also using this to read a user's input, if that helps.
read -p "wcl> " -r wcl $2 $3
I don't really know if there's a better way to solve this without scrapping all my code and starting from scratch.
This is the full code:
#!/bin/bash
#use read command
echo Welcome to the Windows Command Line simulator!
echo Enter your commands below
while true
do
read -p "wcl> " -r wcl $2 $3
case $wcl in
dir)
echo "using Bash command: ls $2 $3"
ls
continue
;;
copy)
FILE="$2"
if [ "$#" -ne 3 ]
then
echo "Usage: copy sourcefile destinationfile"
else
echo "using Bash command: cp $2 $3"
if [ -f "$FILE" ]
then
cp $2 $3
else
echo "cannot stat $FILE: No such file or directory">&2
fi
echo done
fi
continue
;;
del)
if [ -z "$2" ] || [ -z "$3" ]
then
echo "Usage: removes a file"
else
echo "using Bash command: rm $2 $3"
rm $2 $3
echo done
fi
continue
;;
move)
if [ -z "$2" ] || [ -z "$3" ]
then
echo "Usage: moves a file to another file name and location"
else
echo "using Bash command: mv $2 $3"
mv $2 $3
echo done
fi
continue
;;
rename)
if [ -z "$2" ] || [ -z "$3" ]
then
echo "Usage: renames a file"
else
echo "using Bash command: mv $2 $3"
mv $2 $3
echo done
fi
continue
;;
ipconfig)
ifconfig eth0 | grep "inet addr" | cut -d ':' -f 2 | cut -d ' ' -f 1
continue
;;
exit)
echo "Goodbye"
exit 1
;;
^c)
echo "Goodbye"
exit 1
;;
*)
echo "ERROR: Unrecognized command"
continue
esac
done
You can't use read to set the positional parameters, although it isn't clear why you would need to here. Just use regular parameters.
while true
do
read -p "wcl> " -r wcl arg1 arg2
case $wcl in
dir)
echo "using Bash command: ls $arg1 $arg2"
ls "$arg1" "$arg2"
continue
;;
# ...
esac
done
The way read -r wcl $2 $3 is executed is that $2 and $3 are first expanded to give names that read will use to set variables. If those aren't set, then the command reduces to read -r wcl, and so your entire command line is assigned to the variable wcl, not just the command.
However, read by itself is not going to do the same parsing that the shell already does, if you goal is to write your own shell.
If you are really using bash, you can insert the words you read into positional parameters through an array. (You could also just leave them in the array, but the syntax for referring to positional parameters is simpler.)
# -a: read the successive words into an array
read -r -p "wcl> " -a params
# set the positional parameters to the expansion of the array
set -- "${params[#]}"
wcl=$1 # Or you could do the case on "$1"
This will also set $# to the number of words read, as a side-effect of setting the positional parameters.
As #chepner points outs, the read is problematic: It simply splits the input into whitespace-separated words, without respecting quotes, backslashes, and whatever other shell metacharacters you might want to implement. Doing a full bash-style parse of a command-line in bash itself would be quite a difficult exercise.

Creating bash script of a complex linux command

I have few long commands that I will be using on a day to day basis. So I though it would be better to have a bash script where I could pass arguments, thus saving typing. I guess this is the norm in Linux but I am kind of new to it. Could someone show me how to do it. A example is the following command
cut -f <column_number> <filename> | sort | uniq -c |
sort -r -k1 -n | awk '{printf "%-15s %-10d\n", $2,$1}'
so i want this in a script where i can pass the filename and column number (preferably in any order) and get the desired ouput instead of having to type the whole thing everytime.
Create a file say myscript.sh -
#!/bin/bash
if [ $# -ne 2 ]; then
echo Usage: myscript.sh column_number file_path
exit
fi
if ! [ -f $2 ]; then
echo File doesnt exist
exit
fi
if [ `echo $1 | grep -E ^[0-9]+$ | wc -l` -ne 1 ]; then
echo First argument must be a number
exit
fi
cut -f 10 $1 $2 | sort | uniq -c |
sort -r -k1 -n | awk '{printf "%-15s %-10d\n", $2,$1}'
Make sure this file is executable using command chmod +x mytask.sh
You can invoke it like sh myscript.sh 30 myfile.sh or ./myscript.sh 30 myfile.sh
The first line of above script specifies the shell you would like your script to be executed in. $1 and $2 refer to the first and second command line arguments.
About argument validity checks:
First check ensures that there are exactly two arguments passed to the script.
Second check ensures the file pointed by the argument two is existing
Third check ensures that the number passed as first argument is really a number. It uses regular expression for that purpose. May be someone provide a better replacement for this check but this is what came to my mind instantly.
To accept the filename and column number in any order, you'll need to use option switches. Bash's getopts allows you to specify and process options so you can call your script using scriptname -f filename -c 12 or scriptname -c 12 -f filename for example.
#!/bin/bash
options=":f:c:"
while getopts $options option
do
case $option in
f)
filename=$OPTARG
;;
c)
col_num=$OPTARG
;;
\?)
usage_function # not shown
exit 1
;;
*)
echo "Invalid option"
usage_function
exit 1
;;
esac
done
shift $((OPTIND - 1))
if [[ -z $filename || -z $col_num ]]
then
echo "Missing option"
usage_function
exit 1
fi
if [[ $col_num == *[^0-9]* ]]
then
echo "Invalid integer"
usage_function
exit 1
fi
# other checks
cut -f 10 $col_num "$filename" | ...

Resources