shell script works but drop error "line[8] expected argument [" - shell

I have shell script that works (does what i want to do,finds if listed user is online), but each time drop error "line[8] expected argument [". I've tried using == but same thing. There's my code:
#!/bin/sh
truth=0;
until [ $truth -eq 1 ]
do
for i; do
isthere=$(who is here | awk '{print $1}' | grep $i)
if [ $isthere = $i ] #(8 line is here)
then
echo "found user: "$isthere". program now will close.";
exit 0;
fi
done
echo "user not found, retrying after 3sec...";
sleep 3;
done
Thank you for you help and time.

Looks like $isthere or $i is empty. You should quote them: if [ "$isthere" = "$i" ]
In other news: most semicolons are useless; a semicolon it is not a statement terminator, but a statement separator, along with newline.

Related

Bash single-line nested for-loop is taking comparison variable as a command

I believe my error is in do if ($i -gt 100) area but I havent been able to figure it out.
My input is:
for i in `ps | cut -d ' ' -f1`; do if ($i -gt 100); then echo $i; fi; done
My output is this where the process IDs have been taken as commands.
bash: 13968: command not found
bash: 21732: command not found
bash: 21733: command not found
bash: 21734: command not found
How can I fix this and what is the relevant man page that I should read up on? Thank you.
if ($i -gt 100)
should be changed to
if [ $i -gt 100 ]
Note that there is a space before and after [], this is neccessary, otherwise you will get a syntax error (its because [ is a link to test in /usr/bin).
The relevant manapge would be man test, as [ is test.
Also, but this has nothing to do with the question, I recommend switchting from
`command`
to
$(command)
in bash.
It's not clear what your script is trying to do (the posted answers produce no output on my system) but if you want to print all PIDs that are greater than 100, here's how you'd do that:
$ ps | awk '$1 > 100{print $1}'
PID
314024
217880
230804
217084
263048
260788
218016
313464
201556
200732
just add spaces after and before brackets and the expression
for i in `ps | cut -d ' ' -f1`; do if [ $i -gt 100 ]; then echo $i; fi; done

Getting the line count of a file in a shell script with wc failing

my script check if the arguments are files or folders
if it is a file, he count the number of lines
after that, if the number of lines is great then 20 or less he do some instructions
the problem is in this instructionn= cat $a | wc -l
My script:
#!/usr/bin/env bash
echo 'Hello this is the test of' `date`
echo 'arguments number is ' $#
if [ $# -eq 4 ]
then
for a in $#
do
if [ -d $a ]
then
ls $a > /tmp/contenu
echo "contenu modified"
elif [ -f $a ]
then
# this instruction must set a numeric value into n
echo "my bad instruction"
n= cat $a | wc -l
echo "number of lines = " $n
# using the numeric value in a test (n must be numeric and takes the number of lines in the current file)
if [ $n -eq 0 ]
then
echo "empty file"
elif [ $n -gt 20 ]
then
echo ` head -n 10 $a `
else
cat $a
fi
else
echo "no file or directory found"
fi
done
else
echo "args number must be 4"
fi
This is the output of the execution of the incorrect instruction
my bad instruction
5
number of lines =
ExamenEx2.sh: line 19: [: -eq : opérateur unaire attendu
The line n= cat $a | wc -l is an offending instruction. Always remember that bash shell scripting is extremely case-sensitive. Your command is interpreted by the shell as having to run two separate commands
n= cat $a | wc -l
#^^ ^^^^^^^^^^^^^^
#1 2
The first part just stores an empty string to the variable n and the next prints the line count of the file stored in variable a. Notice that the shell does not throw errors for this. Because it is not violating the syntax (just the semantics are wrong). But the line count is never assigned to the variable n.
The error is seen when the conditional if [ $n -eq 0 ] is hit when you are doing a comparison with an empty variable on the LHS.
You wanted to run a command and store its output, you need command-substitution($(..)) for that. Assuming the $a contains a name of a file just do
n=$(wc -l < "$a")
Note, that I've removed the useless cat usage and piping it to wc. But wc can read from an input stream directly.
Also note that you have multiple bad practices in your script. Remember to do the following
Always double-quote the shell variables - "$#", "$#", [ -f "$a" ], [ -d "$a" ]
Don't use the `` for command-substitution, because it is not easily nestable and you might have issues related to quoting also.
You can use conditional expression [[ if you are sure if the script is running under bash in which a variable containing spaces can be used without quoting on the LHS

Return an error if input doesn't have exactly 1 line, otherwise pipe input to next step

I have a series of commands chained together with pipes:
should_create_one_line | expects_one_line
The first command should_create_one_line should produce an output that only has one line, but under strange circumstances it is possible for the output to be multiline or empty.
I would like to add a step in between these two, validate_one_line:
should_create_one_line | validate_one_line | expects_one_line
If its input contains exactly 1 line then validate_one_line will simply output its input. If its input contains more than 1 line or is empty then validate_one_line should cause the whole sequence of steps to stop and return an error code.
What command can I use for validate_one_line?
Use read. Here's a shell function that meets your specs:
exactly_one_line() {
local line # Use to echo the line
read -r line || return # Guarantee at least one line is read
read && return 1 # Indicate failure if another line is successfully read
echo "$line"
}
Notes
"One line" assumes a single line followed by a newline. If your input could be like, a file with contents but no newlines, then this will fail.
Given a pipeline like a|b, a cannot prevent b from running. At a minimum, b needs to handle when a produces no output.
Demo:
$ wc -l empty oneline twolines
0 empty
1 oneline
2 twolines
3 total
$ exactly_one_line < empty; echo $?
1
$ exactly_one_line < oneline; echo $?
oneline
0
$ exactly_one_line < twolines; echo $?
1
First off, you should seriously consider adding the validation code to expects_one_line. According to this post, each process starts in its own subshell, meaning that even if validate_one_line fails, you will get an error in expects_one_line because it will try to run with no input (or a blank line). That being said, here is a bash one-liner that you can insert into your pipe to validate:
should_create_one_line.sh | ( var="$(cat)"; [ $(echo "$var" | wc -l) -ne 1 ] && exit 1 || echo "$var") | expects_one_line.sh
The problem here is that when the validation subshell returns in the exit 1 case, expects_one_line.sh will still get a single blank line. If this works for you, then great. If not, it would be better to just put the following into the beginning of expects_one_line.sh:
input="$(cat)"
[ $(echo "$var" | wc -l) -ne 1 ] && exit 1
This would guarantee that expects_one_line.sh fails properly when getting a single line without having to wonder about what the empty line that the validation outputs will do to the script.
You may find this post helpful: How to read mutliline input from stdin into variable and how to print one out in shell(sh,bash)?
You can use a bash script to check the incoming data and call the other command when the input is only 1 line
The following code starts cat when it is ONLY fet in 1 line
sh -c 'while read CMD; do [ ! -z "$LINE" ] && exit 1; LINE=$CMD; done; [ -z "$LINE" ] && exit 1; printf "%s\n" $LINE | "$0" "$#"' cat
How this works
Try reading a line, if failed go to step 5
If variable $LINE is NOT empty, goto step 6
Save line inside variable $LINE
Goto step 1
If $LINE is NOT empty, goto step 7
Exit the program with status code 1
Call our program and pass our $line to it using printf
Example usage:
Printing out only if grep found 1 match:
grep .... | sh -c 'while read CMD; do [ ! -z "$LINE" ] && exit 1; LINE=$CMD; done; [ -z "$LINE" ] && exit 1; printf "%s\n" $LINE | "$0" "$#"' cat
Example of the question poster:
should_create_one_line | sh -c 'while read CMD; do [ ! -z "$LINE" ] && exit 1; LINE=$CMD; done; [ -z "$LINE" ] && exit 1; printf "%s\n" $LINE | "$0" "$#"' expects_one_line

How to use export in bash shell?

I am making one script to delete all printer jobs older than one day and no of jobs older than one day has to be print also the jobid which has been canceled
Below are few conditions which I have to follow
I do not have access to cups directory , I can not create any temporary file .
I have to use Bash shell.
I tried to use a variable, declared outside while loop but the variable value remains unchanged ~came to know it is because of child process
Tried to use export variable but that also does not work in Bash shell , same is working in ksh shell.
I have tried below logic:
count=0
currDate=`date +%Y%m%d`
lpstat -o|while read line
do
jobid=`echo $line|awk '{print $1}'|cut -d"-" -f2`
jobDate=`echo $line|awk -F ' ' 'BEGIN{OFS="-";} {print $5,$6,$7;}'`
formattedDate=`date -d"${jobDate}" +%Y%m%d`
if [ `expr $currDate - $formattedDate` -gt 1 ]
then
count=`expr $count + 1`
echo " cancelling printer job with jobid $jobid "
cancel $jobid
fi;
done
[ $count -gt 0 ] ; echo "NO of Printer jobs pending more than 1 days are $count";
Not getting how to handle the count variable as the above mentioned way it will not work,
Export is not working in bash shell , creating tmp file is not allowed.
Any suggestion to get the solution.
The problem with count is that you process it inside a new bash child process (bash creates it automatically when you pipe input into while). This is why you get back the initial value.
The solution for you would be to output the value in that child process. This should work (braces are forcing bash to create a child process that includes the output part):
count=0
currDate=`date +%Y%m%d`
lpstat -o| (while read line
do
jobid=`echo $line|awk '{print $1}'|cut -d"-" -f2`
jobDate=`echo $line|awk -F ' ' 'BEGIN{OFS="-";} {print $5,$6,$7;}'`
formattedDate=`date -d"${jobDate}" +%Y%m%d`
if [ `expr $currDate - $formattedDate` -gt 1 ]
then
count=`expr $count + 1`
echo " cancelling printer job with jobid $jobid "
cancel $jobid
fi;
done
[ $count -gt 0 ] && echo "NO of Printer jobs pending more than 1 days are $count")
The simplest solution is to reference the variable in the same shell you are setting it. Syntactically, the easiest thing to do is to add a block:
count=0
currDate=`date +%Y%m%d`
lpstat -o| { while read line # Add opening brace here
do
jobid=`echo $line|awk '{print $1}'|cut -d"-" -f2`
jobDate=`echo $line|awk -F ' ' 'BEGIN{OFS="-";} {print $5,$6,$7;}'`
formattedDate=`date -d"${jobDate}" +%Y%m%d`
if [ `expr $currDate - $formattedDate` -gt 1 ]
then
count=`expr $count + 1`
echo " cancelling printer job with jobid $jobid "
cancel $jobid
fi;
done
echo "Number of Printer jobs pending more than 1 day: $count"
} # Add closing brace here
This is not a code review site, but I feel compelled to address other elements of this code. echo $line | awk '{print $1}' | cut -d"-" -f2 is an atrocity. Rather than reading in an entire line and then parsing it with echo/awk/cut, let read do the parsing for you by assigning IFS appropriately. Stop using backticks. $() notation is better. The last line of this code [ $count -gt 0 ]; echo ... completely ignores the evaluation of $count. It would be better written [ $count -gt 0 ] || echo ... >&2 or test "$count" = 0 && echo ... >&2 (redirect the error message to file descriptor 2, since error belong on stderr).

Unexpected end of file bash script

This is just a simple problem but I don't understand why I got an error here. This is just a for loop inside an if statement.
This is my code:
#!/bin/bash
if (!( -f $argv[1])) then
echo "Argv must be text file";
else if ($#argv != 1) then
echo "Max argument is 1";
else if (-f $argv[1]) then
for i in `cut -d ',' -f2 $argv[1]`
do
ping -c 3 $i;
echo "finish pinging host $i"
done
fi
Error is in line 16, which is the line after fi, that is a blank line .....
Can someone please explain why i have this error ????
many, many errors.
If I try to stay close to your example code:
#!/bin/sh
if [ ! -f "${1}" ]
then
echo "Argv must be text file";
else if [ "${#}" -ne 1 ]
then
echo "Max argument is 1";
else if [ -f "${1}" ]
then
for i in $(cat "${1}" | cut -d',' -f2 )
do
ping -c 3 "${i}";
echo "finish pinging host ${i}"
done
fi
fi
fi
another way, exiting each time the condition is not met :
#!/bin/sh
[ "${#}" -ne 1 ] && { echo "There should be 1 (and only 1) argument" ; exit 1 ; }
[ ! -f "${1}" ] && { echo "Argv must be a file." ; exit 1 ; }
[ -f "${1}" ] && {
for i in $(cat "${1}" | cut -d',' -f2 )
do
ping -c 3 "${i}";
echo "finish pinging host ${i}"
done
}
#!/usr/local/bin/bash -x
if [ ! -f "${1}" ]
then
echo "Argument must be a text file."
else
while-loop-script "${1}"
fi
I have broken this up, because I personally consider it extremely bad form to nest one function inside another; or truthfully to even have more than one function in the same file. I don't care about file size, either; I've got several scripts which are 300-500 bytes long. I'm learning FORTH; fractalism in that sense is a virtue.
# while-loop-script
while read line
do
IFS="#"
ping -c 3 "${line}"
IFS=" "
done < "${1}"
Don't use cat in order to feed individual file lines to a script; it will always fail, and bash will try and execute the output as a literal command. I thought that sed printing would work, and it often does, but for some reason it very often substitutes spaces for newlines, which is extremely annoying as well.
The only absolutely bulletproof method of feeding a line to a script that I know of, which will preserve all space and formatting, is to use while-read loops, rather than substituted for cat or for sed loops, as mentioned.
Something else which you will need to do, in order to be sure about preserving whitespace, is to set the internal field seperator (IFS) to something that you know your file will not contain, and then resetting it back to whitespace at the end of the loop.
For every opening if, you must have a corresponding closing fi. This is also true for else if. Better use elif instead
if test ! -f "$1"; then
echo "Argv must be text file";
elif test $# != 1; then
echo "Max argument is 1";
elif test -f "$1"; then
for i in `cut -d ',' -f2 "$1"`
do
ping -c 3 $i;
echo "finish pinging host $i"
done
fi
There's also no argv variable. If you want to access the command line arguments, you must use $1, $2, ...
Next point is $#argv, this evaluates to $# (number of command line args) and argv. This looks a lot like perl.
Furthermore, testing is done with either test ... or [ ... ], not ( ... )
And finally, you should enclose at least your command line arguments in double quotes "$1". If you don't and there is no command line argument, you have for example
test ! -f
instead of
test ! -f ""
This lets the test fail and go on to the second if, instead of echoing the proper message.

Resources