unexpected return value 123 - linux

I'm trying to do a script that is used with two arguments - a file and an integer. It should check if the arguments are valid, otherwise exit with 1. Then it should either return 0 if the file is smaller than second argument, or echo size of the file to stdout. The script keeps returning value 123 instead of 1 or 0. Where is the problem? Thanks.
#!/bin/bash
if [ $# -eq 2 ];
then
if test $2 -eq $2 > /dev/null 2>&1
then
if [ -f $1 ];
then
if [ $(stat -c %s $1) -ge $2 ];
then
echo $(stat -c %s $1)
else
exit 0
fi
else
exit 1
fi
else
exit 1
fi
else
echo 042f9
exit 1
fi

I do not know where the "123" output comes from, but I would do it like this:
#!/bin/bash
# Must have 2 arguments
if [[ $# -ne 2 ]]
then
printf "042f9\n"
exit 1
fi
# File must exist
if [[ ! -f "$1" ]]
then
exit 1
fi
# File size > $2 check
filesize=$(stat -c %s "$1")
if [[ $filesize -ge $2 ]]
then
printf "%d" "$filesize"
else
exit 1
fi
A couple notes for your scripts (IMHO):
Like Mat mentioned in the comments, test 1 condition and exit right away. When I read your script, I had to go to the end to see what happens if the number of arguments is wrong. Logically there is nothing wrong with your code, it is just making it easier to read.
For bash, use [[ ]] to test if conditions.
I try never to call a function or command twice. That is why I stored the result of the stat command in a variable. If you use it more than once, store it, do not call the command again.
No need for ; since you put your then on the next line anyway.
Always double-quote your variables, especially if they are filenames. Weird filenames break so many scripts!
Finally use printf instead of echo. For simple cases, its the same, but echo does have some issues (https://unix.stackexchange.com/questions/65803/why-is-printf-better-than-echo).
Possible return values:
the size of the file, and the exit value is 0 ($?). The file is larger than argument 2 value.
"042f9", and the exit value is 1 ($?). Arguments error.
nothing, and the exit value is 1 ($?). Missing file error, or the file is smaller than argument 2 value.

Related

Need to verify every file passed as an argument on the command line exists using a shell script

I am looking to create a shell script that reads command line arguments, then concatenates the contents of those files and print it to stdout. I need to verify the files passed to the command line exist.
I have written some code so far, but the script only works if only one command line argument is passed. If passing more than one argument, the error checking I have tried does not work.
#!/bin/bash
if [ $# -eq 0 ]; then
echo -e "Usage: concat FILE ... \nDescription: Concatenates FILE(s)
to standard output separating them with divider -----."
exit 1
fi
for var in "$#"
do
if [[ ! -e $# ]]; then
echo "One or more files does not exist"
exit 1
fi
done
for var in "$#"
do
if [ -f $var ]; then
cat $var
echo "-----"
exit 0
fi
done
I need to fix the error checking on this so that every command line argument is checked to be an existing file. If a file does not exist, the error must be printed to stderr and nothing should be printed to stdout.
You have a bug in line 11:
if [[ ! -e $# ]]; then
You do need to check for a given file here using $var like that:
if [[ ! -e "$var" ]]; then
And you exit prematurely in line 23 - you will always print only a
single file. And remember to always quote your variable because
otherwise your script would not run correctly on files that have a whitespaces in the name, for example:
$ echo a line > 'a b'
$ cat 'a b'
a line
$ ./concat.sh 'a b'
cat: a: No such file or directory
cat: b: No such file or directory
-----.
You said:
if a file does not exist, the error must be printed to stderr and
nothing should be printed to stdout.
You aren't printing anything to stderr at the moment, if you want to
you should do:
echo ... >&2
And you should use printf instead of echo as it's more portable
even though you're using Bash.
All in all, your script could look like this:
#!/bin/bash
if [ $# -eq 0 ]; then
printf "Usage: concat FILE ... \nDescription: Concatenates FILE(s) to standard output separating them with divider -----.\n" >&2
exit 1
fi
for var in "$#"
do
if [[ ! -e "$var" ]]; then
printf "One or more files does not exist\n" >&2
exit 1
fi
done
for var in "$#"
do
if [ -f "$var" ]; then
cat "$var"
printf -- "-----\n"
fi
done
exit 0

What is -z in bash

I am trying to understand the following code:
if [ -z "$1" ] || [ -z "$2" || [ "${3:-}" ]
then
echo "Usage: $0 <username> <password>" >&2
exit 1
fi
I want to understand what we mean by -z "$1" and "${3:-}" in the code.
Please also help me understand >&2 in the code.
1) Your code is not correct, you missed one ] bracket somewhere. Probably after [ -z "$2" block.
2) if statement executes following command(s) and then executes block of code enclosed in then .. fi or then .. else keywords if the return value of the command(s) is true (their exit code is 0)
3) [ is just an alias for the test command (try man test). This command takes several parameters and evaluates them. For example, used with -z "$something" flags would return true (0) if $something is not set or is an empty string. Try it:
if [ -z "$variable" ]; then
echo Variable is not set or is an empty string
fi
4) || statement is an OR. Next command would be executed if the previous one returned false statement. So in the statement
if [ -z "$variable" ] || [ -z "$variable2" ]; then
echo Variable 1 or variable 2 is not set or is an empty string
fi
command [ -z "$variable2" ] would be executed only if variable was empty. The same could be achieved with different syntax:
if [ -z "$variable" -o -z "$variable2" ]; then
echo Variable 1 or variable 2 is not set or is an empty string
fi
which should be faster, because it requires only one instance of the test program to be run. Flag -o means OR, so you could read it as:
If variable is not set/empty OR variable2 is not set/EMPTY...
5) Statement "[ ${3:-} ]" means return true if $3 (the third argument of the script) is set.
6) >&2 is a stream redirection. Every process has two outputs: standard output and error output. These are independent and could be redirected (for example) to be written to two different files. >&2 means "redirect standard output to the same location as standard error".
So to sum up: commands between then .. fi will be executed IF the script is run with $1 empty or $2 empty or $3 NOT empty That means that the script should be run with exactly two parameters. And if not, the echo message will be printed to standard error output.
-z STRING means the length of STRING is zero.
${parameter:-word} If parameter is unset or null, the expansion of word is substituted. In your case $3 is just set with a blank value, if $3 do not have any value.
&2 writes to standard-error. I mean the stdout value of the executed command is sent to stderr,

Script with parameter

I supossed to make a script that given an number it count to 0, I managed to do this and it's working:
#!/bin/bash
echo -n "type a number: "
read number; echo
while [ $number -ge 0 ]; do
echo -n "$number"
number=$((number-1))
done
echo
Well, I changed it because I need to pass the number by an parameter ex: "./script 5" and it must show the countdown till 0, but its getting in looping. I kind new on all it script/stack what Im doing wrong?
#!/bin/bash
if [ "$*" = "" ]; then
echo
echo "not correct"
echo "must be a int number"
echo
exit
fi
while [ "$1" -ge 0 ]; do
echo "$1"
cont='expr $1-1'
done
echo
You're always using [ "$1" -ge 0 ] as your condition, but the value you actually modify/update is cont, not $1. (Moreover, you modify it based on the value of $1, which isn't changing, so you only ever set $cont to one less than the original value of $1).
Consider:
#!/bin/bash
[[ $1 ]] || { printf '%s\n' "First argument must be an integer" >&2; exit 1; }
for ((i=$1; i>=0; i--)); do
echo "$i"
done
...and note, among the various changes:
We're consistently referring to the first argument passed as $1, rather than also sometimes referring to it as $*
When we select a variable to modify ($i, here, rather than $cont), we use that same variable in our tests, and also as the source for modification in the loop.
Using expr for math is antiquated; POSIX sh allows $(( )) to create a math context, and bash extends this to also allow C-style for loops in a math context.

Detect number of argument and the value passed into the bash

I want to make sure my bash script can correctly detect user's input argument. Specifically, user can only pass 1 or 2 or 3 into the script, otherwise the script will be terminated. I wrote the following code:
#!/bin/bash
for args in $#
do
echo $args
done
if [ "$#" != 1 ] && [ "$#" -ne 1 ]; then
echo "Illegal number of parameters"
exit 1
fi
This script can only capture when user does not give any input, but cannot check whether user indeed input the number 1 not other values.
By the way, I am not sure how to express "input argument can accept number 1 or 2 or 3".
$# is an integer, so you have to use integer comparison. You can for example say:
if [ "$#" -ne 1 ]; then
echo "illegal number of parameters"
exit 1
fi
To check that the parameter is either 1, 2 or 3, you can use this regular expression (see something related):
if [[ ! $1 =~ ^(1|2|3)$ ]]; then
echo "the number is not 1, 2 or 3"
exit 1
fi
To express "input argument can accept number 1 or 2 or 3" I would for example say "we can just accept the argument being either 1, 2 or 3".
First off you can detect if the string is null or empty simply by doing the following:
if [ -z "$1" ]
then
echo "Argument $1 contains nothing"
fi
That I would say is your first step, and will allow you to filter out args that have no content.
Following on from that, you'd most likely need to do some comparison work on $1, $2 & $3
I'll just check something and come back to this in a moment.
Update
Just had to go find one of my scripts and check something... :-)
One way I've handled the checking of parms in the past is something like the following
#!/bin/sh
while [ $# -gt 0 ] || [ "$#" -le 4 ]; do
case "$1" in
*[!1-9]*) echo "Text: $1";;
*) echo "Number: $1"
esac
case "$2" in
*[!1-9]*) echo "Text: $2";;
*) echo "Number: $2"
esac
case "$3" in
*[!1-9]*) echo "Text: $3";;
*) echo "Number: $3"
esac
shift
done
Basically a simple regex, if I have more than 0 parameters or less than 4 parameters then I allow it through to a case statement, which then checks the content of each parameter.
This one just has an echo in, but you could just as easy set some flags, and then decide how to continue based on those flags.
For simple range checking however, you might just want to use a one liner similar to the following:
if [[ $# -gt 0 && $# -lt 4 ]]; then echo "Correct number of parameters"; fi
Again setting a flag to use later rather than echoing the results.
I assume you mean the input can only be 1, 2 or 3, so using a case statement is the best way. $1 is the variable that stores your argument, if it is equal to 1 case will execute the code in the block corresponding to the value 1 and so on.
case "$1" in
1) ...
;;
2) ...
;;
3) ...
;;
*) echo "Invalid argument"
;;
esac

Erroring out for passed argument less than 1, what am I doing?

I'm trying to get a script to echo a message when a number like -9 is entered.
The arguments have to be passed from the command line
This is what I have now.
#!/bin/bash
#Assign1part1
if (( $# != 1 )); then
echo "Error: Must only enter one argument" >&2
exit 1
fi
if (( $1 -lt 1 )); then
echo "Error: Argument must be a positive integer" >&2
exit 1
fi
seq -s, $1 -1 1
(( ... )) is not test.
$ (( -1 < 1 )) ; echo $?
0
$ (( -1 > 1 )) ; echo $?
1
You need to use [[ and ]], not (( and )). The former is testing, the latter is expression evaluation which allows for != but not -lt.
On top of that, your first error message is slightly off, making it sound like you've entered more arguments than you should have, even in the case where you enter none. It would be better phrased as something like "Must enter exactly one argument".
And, since $# is numeric, I prefer using the numeric comparisons, -ne rather than != in this particular case.
In other words:
#!/bin/bash
#Assign1part1
if [[ $# -ne 1 ]]; then
echo "Error: Must enter exactly one argument" >&2
exit 1
fi
if [[ $1 -lt 1 ]]; then
echo "Error: Argument must be a positive integer" >&2
exit 1
fi
seq -s, $1 -1 1
Running that with certain test data gives:
pax> testprog 5
5,4,3,2,1
pax> testprog 9
9,8,7,6,5,4,3,2,1
pax> testprog
Error: Must enter exactly one argument
pax> testprog 1 2
Error: Must enter exactly one argument
pax> testprog -7
Error: Argument must be a positive integer

Resources