Shell Programming with Modulus Operation - shell

I'm trying to write a shell program that, given an argument, prints the name of the program and every odd word in the argument (that is, not even words). However, I am not getting the expected result. Upon tracing my program, I notice that, despite modulus returning values of 1 on odd words (say, the 5th word, 5 % 2 = 1), the program still treats the result as 0 (an even word) and doesn't print the word. What might be going wrong here?
Included here is my code and the traced output to see exactly what I am getting. (Sorry for not including the code as text, I'm new to vim and don't know copy/paste yet)

$result (needs a dollar sign )

Change echo \$$# to echo $1. But it would probably be simpler to re-write the script:
#!/bin/sh
echo $0
while [ $# -gt 0 ]; do
expr $# % 2 > /dev/null && echo $1
shift
done

Related

Trying to create a bash script that adds positive integers taken from input on a single line with spaces

Im trying to take input from the terminal one line with spaces in between for example input would be something like this.
-1 -1 1
then im trying to take that input and add them together.
the catch is im only supposed to add positive integers so i need to remove the dashes.
I have
read Str
str=( $Str );
arr=${str//-/}
echo "$((${arr[#]/%/+}0))"
it seems like its only removing one instance of the dash and not the rest. Not sure which direction to take. Im sure there are multiple solutions.
Any help would be appreciated. I was also thinking maybe an If statement that could remove dashes before adding but not sure how exactly to even begin that.
If you just want to accumulate the words one at a time into a sum, removing any optional leading - characters, you can do that with something like:
read line
((sum = 0))
for num in ${line} ; do
absnum=${num//-/}
((sum += absnum))
done
echo "Sum is" ${sum}
Your method of removing leading negative sign is sound, but you don't really need to create an array to do the work. You can just iterate over the words as shown.
Keep in mind a real program should be a little more robust, handling non-numerics and such without falling in a screaming heap. You could do something like this immediately after the assignment to absnum:
[[ ! "${absnum}" =~ ^[1-9][0-9]*|0$ ]] && echo Bad number ${num} && exit 1
This would ensure it was a valid non-negative integer. A sample run follows so you can adjust to whatever test data you'd like to use:
pax> echo '3 0 --1 -2 4 40' | bash myscript.bash
Sum is 50

How can I access a file in bash and compare its contents with stdout when running a program to make sure they are identical?

How can I to compare the output in stdout of a program with a model output in an output file? I ask because I am trying to make a grading script. Also, I am thinking of using -q grep, but am not sure how I would use it still.
Please make answer simple because I am a noob at bash.
Important edit:
I want to use this in an if statement. For example:
if(modle_file.txt is identical to stdout when running program); then
echo "Great!"
else
echo "Wrong output. You loose 1 point."
Edit:
The program takes an input. So for example if we do:
%Python3 Program.py
Enter a number: 5
The first 5 (arbitrary) things are:
2, 5, etc (program output)
%
If your file is called example.txt, do
diff example.txt <(program with all its options)
The <() syntax takes the output of the program in parentheses and passes it to the diff command as if it was a text file.
EDIT:
If you just want to check whether the text file and the output of the program are the same or not in an if-clause, you can do:
if [ "$(diff example.txt <(program with all its options))" == "" ]; then
echo 'the outputs are identical'
else
echo 'the outputs differ'
fi
I.e. diff only generates output if the files differ, so an empty string as answer means the files are identical.
EDIT 2:
In principle you can re-direct stdin to a file, like so:
program < input.txt
Now, without further testing, I don't know whether this will work with your python script, but suppose you can put all the input the program expects into such a file, you could do
if [ "$(diff example.txt <(program < input.txt))" == "" ]; then
echo 'Great!'
else
echo 'Wrong output. You loose 1 point.'
fi
EDIT 3::
I wrote a simple test program in python (let's call it program.py):
x = input('type a number: ')
print(x)
y = input('type another number: ')
print(y)
If you run it interactively in the shell with python program.py, and give 5 and 7 as answers, you get the following output:
type a number: 5
5
type another number: 7
7
If you create a file, say input.txt, which contains all the desired input,
5
7
and pipe that into your file like so:
python program.py < input.txt
you get the following output:
type a number: 5
type another number: 7
The reason for the difference is that python (and many other shell programs) treat input differently depending on whether it comes from an interactive shell, a pipe, or a redirected stdin. In this case, input is not echoed as the input comes from input.txt. However, if you run both your code and the student's code using input.txt, the two outputs should still be comparable.
EDIT 4:
As one of the comments states below, it is not necessary to compare the entire output of the diff command against an empty string ("") if you only want to know whether they differ, the return status is enough. It's best to write a small test script in bash (let's call it code_checker.sh),
if diff example.txt <(python program.py < input.txt) > /dev/null; then
echo "Great!"
else
echo "Wrong output. You loose 1 point."
fi
the >/dev/null part in the if-clause re-directs the output of diff to a special device, effectively ignoring it. If you have a lot of output, it might be better to use cmp like mentioned by user1934428.
One way to do this would be to redirect the stdout output to a file, like this:
myCommand > /folder/file.txt
And then run a diff command to compare the two files
diff /folder/file.txt /folder/model_output.txt
Edit: To use this on an if statement, you could do the following:
if [ -z "$(diff /folder/file.txt /folder/model_output.txt 2>&1)" ]; then echo "Great!"; else echo "Wrong output. You loose 1 point."; fi
If the files are equal, it will print Great!, otherwise it will print Wrong output. You loose 1 point.
Since you are not interested in the actual differences, but only in whether they are identical, I think cmp is the best choice (and faster if the files are large):
if cmp -s example.txt <(your program goes here)
then
echo identical
fi
Note that this compares only stdout (as you requested), not stderr.

First time on BASH

This is my first time using bash in college and it's being pretty hard now.
The exercise is:
Make a shell script which receives by parameter one word and a file list and adds the word in the beggining and ending of each file
So far what I've done is this:
#!bin/bash
word=$1;
i=2;
j=2;
for [ i -le $# ] in ls
do
for [ j -le $# ] in ls
do
if [ $i = $j ] then
$j=`word+$j+word`;
fi
done
done
and of course it doesn't work, but I really don't know why.
If anybody could help, it'd be great.
Sorry by any language mistake or convention in SO, I just arrived here. Thank you very much!
Since it's an exercise I'll give the answer in the way I would have wanted to learn about it:
Your script needs to take an arbitrary number of arguments - for example ./my_script.sh "my word" *.txt (note the space and quotes in the first parameter). There is a shell builtin command called shift which will remove the first argument from the argument list. The argument list is commonly referred to using "$#", but there is a handy shortcut syntax in Bash to loop over all arguments which avoids it entirely:
for argument
do
something with "$argument"
done
The exercise as originally stated says to add the string to the start and end of each file, not filename. There are plenty of examples of how to do that on this site and unix.SE.
You'll want to be careful about the difference between [ (aka. test) and [[.
Bash is not C - the ; command terminator is implicit at end of line (except of course in multi-line strings, here documents and the like).

Arguments not found in shell script

I am trying to write my first shell script for a class. The goal is to take a list of integers as a command line argument and display their squares and the sum of the squares. I am getting an error that the arguments are not being found.
This is the piece that is giving the error that the arguments are not found:
sumsq=0 #sum of squares
int=0 #Running sum initialized to 0
count=0 #Running count of numbers passed as arguments
while [ $# != 0 ]
do
numbers[$int]=`expr $1` #Assigns arguments to integers
let square=`expr $1*$1` #Operation to square arguments
squares[$int]=$square #Calc. square of each argument
sumsq=`expr $sumsq + $square` #Add square to total
count=`expr $count + 1` #Increment count
shift #Remove the used argument
int=`expr $int + 1` #Increment to next argument
done
I am using dash shell.
It seems that you are a bash beginner, some good pointers to start learning:
FAQ: http://mywiki.wooledge.org/BashFAQ
Guide: http://mywiki.wooledge.org/BashGuide
Ref: http://www.gnu.org/software/bash/manual/bash.html
http://wiki.bash-hackers.org/
http://mywiki.wooledge.org/Quotes
Check your script: http://www.shellcheck.net/
And avoid people saying to learn with tldp.org web site, the tldp bash guide is outdated, and in some cases just plain wrong.
There's many things in your code that can be improved. Better learn the good way as soon as possible. Your code looks 80's =)
A corrected version (not tested) with a more bashy way to do things:
sumsq=0 #sum of squares
int=0 #Running sum initialized to 0
count=0 #Running count of numbers passed as arguments
while (($# != 0 )); do
numbers[$int]=$1 #Assigns arguments to integers array
square=$(($1*$1)) #Operation to square argument first arg by itself
squares[$int]=$square #Square of each argument
sumsq=$((sumsq + square)) #Add square to total
count=$((count++)) #Increment count
shift #Remove the used argument
done
Dash doesn't support arrays, Bash does.
If you are running the script interactively you might not have bash configured as your default shell, run bash before trying.
If you are running it from console:
bash script.sh
If you are running it using its path (for example ./script.sh) ensure the first line of the script is:
#!/bin/bash
And not:
#!/bin/sh

Bash parameter expansion

I have a script which uses the following logic:
if [ ! -z "$1" ]; then # if any parameter is supplied
ACTION= # clear $ACTION
else
ACTION=echo # otherwise, set it to 'echo'
fi
This works fine, as-is. However, in reading the Shell Parameter Expansion section of the bash manual, it seems this should be able to be done in a single step. However, I can't quite wrap my head around how to do it.
I've tried:
ACTION=${1:-echo} # ends up with $1 in $ACTION
ACTION=${1:+}
ACTION=${ACTION:-echo} # ends up always 'echo'
and a few ways of nesting them, but nesting seems to be disallowed as far as I can tell.
I realize I've already got a working solution, but now I'm genuinely curious if this is possible. It's something that would be straightforward with a ternary operator, but I don't think bash has one.
If this is possible, I'd like to see the logic to do this seeming two-step process, with no if/else constructs, but using only any combination of the Shell Parameter Expansion features.
Thank you.
EDIT for elderarthis:
The remainder of the script is just:
find . -name "*\?[NMSD]=[AD]" -exec ${ACTION} rm -f "{}" +
I just want ACTION=echo as a sanity check against myself, hence, passing any argument will actually do the deletion (by nullifying ${ACTION}, whereas passing no args leaves echo in there.
And I know TIMTOWTDI; I'm looking to see if it can be done with just the stuff in the Shell Parameter Expansion section :-)
EDIT for Mikel:
$ cat honk.sh
#!/bin/bash
ACTION=${1-echo}
echo $ACTION
$ ./honk.sh
echo
$ ./honk.sh foo
foo
The last needs to have ACTION='', and thus return a blank line/null value.
If I insisted on doing it in fewer than 4 lines and no sub-shell, then I think I'd use:
ACTION=${1:+' '}
: ${ACTION:=echo}
This cheats slightly - it creates a blank action rather than an empty action if there is an argument to the script. If there is no argument, then ACTION is empty before the second line. On the second line, if action is empty, set it to 'echo'. In the expansion, since you (correctly) do not quote $ACTION, no argument will be passed for the blank.
Tester (xx.sh):
ACTION=${1:+' '}
: ${ACTION:=echo}
echo $ACTION rm -f a b c
Tests:
$ sh xx.sh 1
rm -f a b c
$ sh xx.sh
echo rm -f a b c
$ sh xx.sh ''
echo rm -f a b c
$
If the last line is incorrect, then remove the colon from before the plus.
If a sub-shell is acceptable, then one of these two single lines works:
ACTION=$([ -z "$1" ] && echo echo)
ACTION=$([ -z "${1+X}" ] && echo echo)
The first corresponds to the first version shown above (empty first arguments are treated as absent); the second deals with empty arguments as present. You could write:
ACTION=$([ -z "${1:+X}" ] && echo echo)
to make the relation with the second clearer - except you're only going to use one or the other, not both.
Since the markdown notation in my comment confused the system (or I got it wrong but didn't get to fix it quickly enough), my last comment (slightly amended) should read:
The notation ${var:+' '} means 'if $var is set and is not empty, then use what follows the +' (which, in this case, is a single blank). The notation ${var+' '} means 'if $var is set - regardless of whether it is empty or not - then use what follows the +'. These other expansions are similar:
${var:=X} - set $var to X unless it already has a non-empty value.
${var:-X} - expands to $var if it has a non-empty value and expands to X if $var is unset or is empty
Dropping the colon removes the 'empty' part of the test.
ACTION=${1:-echo}
is correct.
Make sure it's near the top of your script before anything modifies $1 (e.g. before any set command). Also, it wouldn't work inside a function, because $1 would be the first parameter to the function.
Also check if $1 is set but null, in which case fix how you're calling it, or use ACTION=${1-echo} (note there is no :).
Update
Ah, I assumed you must have meant the opposite, because it didn't really make sense otherwise.
It still seems odd, but I guess as a mental exercise, maybe you want something like this:
#!/bin/bash
shopt -s extglob
ACTION=$1
ACTION=${ACTION:-echo}
ACTION=${ACTION/!(echo)/} # or maybe ACTION=${ACTION#!(echo)}
echo ACTION=$ACTION
It's not quite right: it gives ACTION=o, but I think something along those lines should work.
Further, if you pass echo as $1, it will stay as echo, but I don't think that's a bad thing.
It's also terribly ugly, but you knew that when asking the question. :-)

Resources