Bash - not enough arguments - bash

I have never used bash before but I am trying to understand this piece of code. The script is supposed to display all log in names, full names and their user-ids. However, whenever I run I can not get past the first if statement and if I delete the statement, it does not work.
#!/bin/bash
if [ $# -lt 1 ];
then
printf "Not enough arguments - %d\n" $#
exit 0
fi
typeset user=""
typeset name=""
typeset passwdEntry=""
while [ $# -ge 1 ];
do
user=$1
shift
name=""
passwdEntry=`grep -e ^$user /etc/passwd 2>/dev/null`
if [ $? -eq 0 ]; then
name=`echo $passwdEntry|awk -F ':' '{print $5}'`
fi
echo "$user $name"
done

$# means "the number of arguments to the current Bash program", and $1 means "the first argument to the current Bash program".
So your problem is that you're not passing any arguments to the program; for example, instead of something like this:
./foo.sh
you'll need to write something like this:
./foo.sh USERNAME
As you are new to Bash, I highly recommend skimming and bookmarking the Bash Reference Manual, http://www.gnu.org/software/bash/manual/bashref.html. It's all on a single page, so you can use your browser's "find in page" function (typically Ctrl+F) to search for things.

Related

Shell script with absolute path and control errors

I was doing this little script in which the first argument must be a path to an existing directory and the second any other thing.
Each object in the path indicated in the first argument must be renamed so that the new
name is the original that was added as a prefix to the character string passed as the second argument. Example, for the string "hello", the object OBJECT1 is renamed hello.OBJECT1 and so on
Additionally, if an object with the new name is already present, a message is shown by a standard error output and the operation is not carried out continuing with the next object.
I have the following done:
#! /bin/bash
if [ "$#" != 2 ]; then
exit 1
else
echo "$2"
if [ -d "$1" ]; then
echo "directory"
for i in $(ls "$1")
do
for j in $(ls "$1")
do
echo "$i"
if [ "$j" = "$2"."$i" ]; then
exit 1
else
mv -v "$i" "$2"."$i"
echo "$2"."$i"
fi
done
done
else
echo "no"
fi
fi
I am having problems if I run the script from another file other than the one I want to do it, for example if I am in /home/pp and I want the changes to be made in /home/pp/rr, since that is the only way It does in the current.
I tried to change the ls to catch the whole route with
ls -R | sed "s;^;pwd;" but the route catches me badly.
Using find you can't because it puts me in front of the path and doesn't leave the file
Then another question, to verify that that object that is going to create new is not inside, when doing it with two for I get bash errors for all files and not just for coincidences
I'm starting with this scripting, so it has to be a very simple solution thing
An obvious answer to your question would be to put a cd "$2 in the script to make it work. However, there are some opportunities in this script for improvement.
#! /bin/bash
if [ "$#" != 2 ]; then
You might put an error message here, for example, echo "Usage: $0 dir prefix" or even a more elaborate help text.
exit 1
else
echo $2
Please quote, as in echo "$2".
if [ -d $1 ]; then
Here, the quotes are important. Suppose that your directory name has a space in it; then this if would fail with bash: [: a: binary operator expected. So, put quotes around the $1: if [ -d "$1" ]; then
echo "directory"
This is where you could insert the cd "$1".
for i in $(ls $1)
do
It is almost always a bad idea to parse the output of ls. Once again, this for-loop will fail if a file name has a space in it. A possible improvement would be for i in "$1"/* ; do.
for j in $(ls $1)
do
echo $i
if [ $j = $2.$i ]; then
exit 1
else
The logic of this section seems to be: if a file with the prefix exists, then exit instead of overwriting. It is always a good idea to tell why the script fails; an echo before the exit 1 will be helpful.
The question is why you use the second loop? a simple if [ -f "$2.$i ] ; then would do the same, but without the second loop. And it will therefore be faster.
mv -v $i $2.$i
echo $2.$i
Once again: use quotes!
fi
done
done
else
echo "no"
fi
fi
So, with all the remarks, you should be able to improve your script. As tripleee said in his comment, running shellcheck would have provided you with most of the comment above. But he also mentioned basename, which would be useful here.
With all that, this is how I would do it. Some changes you will probably only appreciate in a few months time when you need some changes to the script and try to remember what the logic was that you had in the past.
#!/bin/bash
if [ "$#" != 2 ]; then
echo "Usage: $0 directory prefix" >&2
echo "Put a prefix to all the files in a directory." >&2
exit 1
else
directory="$1"
prefix="$2"
if [ -d "$directory" ]; then
for f in "$directory"/* ; do
base=$(basename "$f")
if [ -f "Sdirectory/$prefix.$base" ] ; then
echo "This would overwrite $prefix.$base; exiting" >&2
exit 1
else
mv -v "$directory/$base" "$directory/$prefix.$base"
fi
done
else
echo "$directory is not a directory" >&2
fi
fi

How can I make bash evaluate IF[[ ]] from string?

I am trying to create a "Lambda" style WHERE script.
I want lambdaWHERE to take piped input and pass it through if condition after given as arguments is met. Like xargs I use {} to represent what comes down the pipe.
I call command like:
ls -d EqAAL* | lambdaWHERE.sh -f {}/INFO_ACTIVETICK
I want the folder names passed through if they contain a file called INFO_ACTIVETICK
Here is the script:
#!/bin/sh
#set -x
ARGS=$*
while read i
do
CMD=`echo $ARGS | sed 's/{}/'$i'/g'`
if [[ $CMD ]]
then
echo $i
fi
done
But when I run it a mysterious "-n" appears...
$ ls -d EqAAL* | /q/lambdaWHERE.sh -f {}/INFO_ACTIVETICK
+ ARGS='-f {}/INFO_ACTIVETICK'
+ read i
++ echo -f '{}/INFO_ACTIVETICK'
++ sed 's/{}/EqAAL-1m/g'
+ CMD='-f EqAAL-1m/INFO_ACTIVETICK'
+ [[ -n -f EqAAL-1m/INFO_ACTIVETICK ]]
+ echo EqAAL-1m
EqAAL-1m
+ read i
How can I get the bit in the [[ ]] correct?
You were quite close. you only need to switch to the standard POSIX [ $CMD ] and it will work.
The main difference between using [[ $CMD ]] and [ $CMD ] is that the first has fewer surprises and you need not quote variables. That also means that a variable is though of as one token and cannot have a whole expression in it like you are trying. [ $CMD ] however works the same way as the original shell where [ was just a command an thus need explicit quotations in order to interpret something with spaces as one argument.
There is a relevant question about the differences between [[ ...]] and [ ..]

How can I test if files given as an argument exist?

I am making a bash script that you have to give 2 files or more as arguments.
I want to test if the given files exist. I'm using a while loop because I don't know how many files are given. The problem is that the if statement sees the $t as a number and not as the positional parameter $number. Does somebody have a solution?
t=1
max=$#
while [ $t -le $max ]; do
if [ ! -f $t ]; then
echo "findmagic.sh: $t is not a regular file"
echo "Usage: findmagic.sh file file ...."
exit
fi
t=`expr $t + 1`
done
You can do it with the bash Special parameter # in this way:
script_name=${0##*/}
for t in "$#"; do
if [ ! -f "$t" ]; then
echo "$script_name: $t is not a regular file"
echo "Usage: $script_name file file ...."
exit 1
fi
done
With "$#" you are expanding the positional parameters, starting from one as separate words (your arguments).
Besides, remember to provide a meaningful exit status (e.g. exit 1 instead of exit alone). If not provided, the exit status is that of the last command executed (echo in your case, which succes, so you're exiting with 0).
And for last, instead of write the script name (findmagic.sh in your case), you can set a variable at the beginning in your script:
script_name=${0##*/}
and then use $script_name when necessary. In this way you don't need to update your script if it changes its name.

Bourne Shell Programming: handling argument errors

I am writing a shell program that takes in three arguments:
an integer to determine the function of the program
a file used by the program
The command is of the form myProgram num file. However, I want the program to output an error if the command only has 0, 1, or more than 2 arguments. That is, if I type "myProgram", "myProgram num", or "myProgram num file anotherWord", an error will be printed to the screen. Does anyone know how I could implement this into my existing code?
In bash, when using integers, the (( )) is more intuitive :
#!/bin/bash
if (($# < 2)); then
printf >&2 "There's less than 2 arguments\n"
exit 1
fi
if (($# > 2)); then
printf >&2 "There's more than 2 arguments\n"
exit 1
fi
if ! (($1)); then
printf >&2 "First argument must be a positive integer\n"
exit 1
fi
if [[ ! -f "$2" ]]; then
printf >&2 "Second argument must be an exited file\n"
exit 1
fi
# -->the rest of the script here<--
Moreover, to respect the best practice & proper coding, when printing an error, it must be so STDERR like I do with printf >&2
The built-in variable $# contains the number of arguments that were passed to the script. You use this to check if there are enough arguments like so:
#!/bin/bash
if [ $# -ne 2 ]; then
echo "Usage: myProgram num file" >&2
exit 1
fi
# The rest of your script.
Use shell built in $# to determine the number of arguments passed into your script. Your program name is not counted.
if you are using bash, then you can approach it like this:
#!/bin/bash
if [ $# -lt 2 ] || [ $# -gt 3 ]
then
echo "You did not provide the correct parameters"
echo "Usage: blah blah blah"
fi
This is a very simple check. You can also check man pages for getopt processing which is much more powerful when evaluating command line parameters.
be well

bash picking arguments

I want to write a function for when I have something like the following
echo 1 2 3|pick
Pick will then take the arguments and I will do something with them.
How do I do this?
Are you looking for xargs?
pick() {
read -r arg1 arg2 remainder
echo first arg is $arg1
echo The remaining args are $remainder
}
--EDIT (response to question in comment)
One way to loop through the arguments:
pick() {
read args;
set $args;
while test $# -ne 0; do
echo $1
shift
done
}
On each iteration of the loop, $1 refers to an argument.
If I'm not mistaken, the OP wants the same thing I do: you feed it a string, and if the string containes multiple {words,lines}, it presents you a menu, and you pick one, and it returns the one you pick on stdout.
If there's only one item, it just returns it.
This is useful for--to use my particular use-case--a log file viewer script: you give it a substring of a filename, and it greps through find /var/log -name \*$arg\* -print to see what it can find. If it gets a unique hit, it hands it back to your script, which runs less against it. If it gets more than one hit, it shows you a menu, and lets you pick one.
ISTR that KSH has a builtin for this, but that I wasn't all that impressed with it; I don't recall if bash has one.
I am here because I was searching to see if someone had already written it before writing it myself. :-)
UPDATE: Nope; I wrote it myself:
Here's some example code:
/usr/local/bin/msg:
PATH=$PATH:/usr/local/bin
[ $UID = 0 ] || exec sudo su root -c "$0 $*"
FILE=/var/log/messages
[ $# -eq 1 ] &&
FILE=`find /var/log/ -name \*$1\* -print |
egrep -v '2011|.[0-9]$' |
pick`
echo "$FILE"
less +F $FILE
Since I'm piping the name to less +F I want to grep out archived log files; this is for interactive log viewing.
/usr/local/bin/pick:
# Present the user a bash Select menu, and let them pick
# Try to be smart about multi-line responses
# must take input on stdin if it might be multiline
# get multiline input from stdin
while read LINE </dev/stdin
do
CHOICES+=( $LINE )
done
# add on anything specified as arguments
while [ $# -gt 0 ]
do
CHOICES+=( $1 )
shift
done
# if only one thing to pick, just pick it
if [ ${#CHOICES[*]} -eq 1 ]
then
echo $CHOICES
exit
fi
# eval set $CHOICES
select CHOSEN in ${CHOICES[#]}
do
echo $CHOSEN
exit
done </dev/tty

Resources