Shell Script - Pair numbers with a for statement - bash

I'm trying to make a script that adds pairs of number, using only simple for/if statements, for example:
./pair 1 2 4 8 16
would result in
./pair 1 2 4 8 16 32
3
12
48
My current script is (probably hugely wrong, I'm new to Shell Scripting):
#!/bin/sh
sum=0
count=0
for a in $*
do
sum=`expr $sum + $a`
count=`expr $count + 1`
if [ count=2 ]
then
sum=0
count=0
fi
echo "$sum"
done
exit
However this is not working. Any help would be great.

A for loop isn't really the right tool for this. Use a while loop and the shift command.
while [ $# -gt 1 ]; do
echo $(( $1 + $2 ))
shift 2
done
The problem in your script is that you do not have sufficient whitespace in your if statement, as well as not preceding the variable name with a $:
if [ $count = 2 ]
Also, you only want to output $sum just before you reset its value to 0, not every time through the loop.
And, expr isn't needed for arithmetic anymore: sum=$(( sum + a ))

My approach uses cshell. As a C programmer, I connect better with cshell. But that asside, I approach the problem with an iterator, incrementing twice.
macetw
I haven't seen anyone write in Csh for a long time. Back in the early Unix days, there were two primary shells: Csh and Bourne shell. Bourne was the AT&T flavor and BSD (Berkeley Standard Distribution) used Csh which took syntax hints from the C language.
Most people I knew used Csh as their standard shell, but wrote scripts in Bourne shell because Csh had all sorts of issues as pointed out by Tom Christiansen (as already mentioned by Olivier Dulac). However, Csh had command aliases and command line history and editing. Features that people wanted in their command line shell.
Csh peaked back in the SunOS days. SunOS was based upon BSD and Sun administrators wrote almost all of their scripts in Csh. However, David Korn changed a lot of that with Kornshell. Kornshell contained the standard Bournshell syntax language, but contained features like command aliasing and shell history editing that people wanted in Csh. Not only that, but it contained a lot of new features that were never previously found in shells like built in math (goodbye to expr and bc), pattern matching with [[ ... ]] and shell options that could be set with set -o.
Now, there was little need to know Csh at all. You could use Kornshell as both your scripting language and your command line shell. When Sun replaced SunOS with Solaris, the Kornshell and not the C shell was the default shell. That was the end of C shell as a viable shell choice.
The last big advocate of Csh was Steve Jobs. NeXT used the TurboCsh as its default shell, and it was the default shell on the first iteration of Mac OS X -- long after Sun had abandoned it. However, later versions of Mac OS X defaulted to BASH.
BASH is now the default and standard. As I mentioned before, on most systems, /bin/sh is BASH. In the BASH manpage is this:
If bash is invoked with the name sh, it tries to mimic the startup behavior of historical versions of sh as closely as possible, while conforming to the POSIX standard as well.
That means that solid BASHisms (shopt, ${foo/re/repl}, etc.) are not active, but most of the stuff inherited from Kornshell ([[ ... ]], set -o, typeset, $(( ... )) ) are still available. And, that means about 99% of the BASH scripts will still work even if invoked as /bin/sh.
I believe that most of the criticisms in Tom Christiansen anti-Csh paper no longer apply. Turbo C shell - which long ago replaced the original C shell fixed many of the bugs. However, Turbo C came after much of the world abandoned C shell, so I really can't say.
The Csh approach is interesting, but is not a correct answer to this question. C Shell scripting is a completely different language than Bourne shell. You'd be better off giving an answer in Python.

My approach uses cshell. As a C programmer, I connect better with cshell. But that asside, I approach the problem with an iterator, incrementing twice.
# i = 1
while ( $i < $#argv )
# j = $i + 1
# sum = $argv[$i] + $argv[$j]
echo $sum
# i = $i + 2
end

So the shebangs inplies bourne shell, and if you start the script directly it will be the same as doing:
/bin/sh <your_script_name>
And the simplest loop I can think of would be this one:
while [ $# -gt 1 ]; do
expr $1 + $2
shift 2
done
It counts the input tokens and as long as you have more than 2 (the square brackets test) it adds the first and the second one displaying the result. shift 2 means shifting the input two places (i.e. $1 and $2 are dropped and $n will be mapped to $n-2).

If you want a funny sh/bc combo solution:
#!/bin/sh
printf "%s+%s\n" "$#" | bc
It's cool, printf takes care of the loop itself. :)
Of course, there are no error checkings whatsoever! it will work with floats too.
The same with dc:
#!/bin/sh
printf "%s %s+pc" "$#" | dc
but it won't work with negative numbers. For negative numbers, you could:
#!/bin/sh
printf "%s %s+pc" "${#/-/_}" | dc
And I just realized this is the shortest answer!

Related

Bash Script "integer expression expected" , (floats in bash) replace part with awk

I wrote a little bash-script to check my Memory-usage and warn me if it's to high.
now my problem is, that I would like to keep the floating-value instead of just cutting it away. but I'm not able to do it..
I would prefer awk over anything else as it's pre-installed on many systems and I already use it in the script.
#!/bin/bash
#define what values are too high (%)
inacceptableRAM="90"
#check RAM %
RAM=`free | grep Mem | awk '{print $3/$2 * 100.0}'`
#send Alarm for RAM
if [ $RAM -ge $inacceptableRAM ] ; then
echo Alarm RAM usage is #$RAM"
fi
so how can I replace my if -ge using awk?
I think it should look something like:
awk ' $RAM >= $inacceptableRAM '
but what do I need to do to make it work inside the bash script?
Since you're comparing with an integer, you can just trim off the decimal part when comparing:
if [ "${RAM%.*}" -ge "$inacceptableRAM" ] ; then
If you want to do it entirely in awk, the only tricky thing is that you have to use -v var=value to convert the inacceptableRAM shell variable into an awk variable:
free | awk -v limit="$inacceptableRAM" '/Mem/ {ram=$3/$2*100; if (ram>=limit) print ram}'
Note that I'm using /Mem/ in the awk script to effectively replace the grep command. Piping from grep to awk is almost never necessary, since you can just do it all in awk.
Other recommendations: use $( ) instead of backticks for command substitutions (see BashFAQ #82), and use lower- or mixed-case variable names (e.g. ram instead of RAM) to avoid accidentally using one of the many all-caps names that have special meanings (see this answer).
An alternative to awk is bc , something like:
#!/usr/bin/env bash
#define what values are too high (%)
inacceptableRAM="90"
#check RAM %
ram=$(free | awk '/Mem/{print $3/$2 * 100.0}')
#send Alarm for RAM
if (( $(bc <<< "$ram > $inacceptableRAM") )) ; then
echo "Alarm RAM usage is #$ram"
fi
You're trying to do too much in shell. A shell is a tool to manipulate files and process and sequence calls to other tools. The tool that the guys who created shell also created for shell to call to manipulate text is awk:
#!/usr/bin/env bash
free |
awk '
BEGIN {
#define what values are too high (%)
unacceptableRam = 90
}
/Mem/ {
#check RAM %
ram = ( $2 ? $3 / $2 * 100 : 0 )
#send Alarm for RAM
if ( ram >= unacceptableRam ) {
print "Alarm RAM usage is #" ram
}
}
'
It's worth considering if it's time for you to "upgrade" to a proper programming language with support for features like floating point arithmetic. Bash and shell scripting is great, but it runs up against limitations very quickly. Even Google's Shell Style Guide suggests changing languages when things get complicated. It's likely you could get a Python script doing exactly what your Bash script is doing with just a few more lines of code.
That said, I am very guilty of leaning on Bash even when I probably shouldn't :)
I recently wanted to compute a floating point average in a Bash script and decided to delegate to a Python subprocess just for that task, rather than swap over fully to Python. I opted for Python because it's almost as ubiquitous as Bash, easy to read, and extensible. I considered bc but was worried it might not be installed on all systems. I didn't consider awk because, as great a tool as it is, its syntax is often opaque and arcane. Python is probably slower than bc or awk but I really don't care about that incremental performance difference. If I did care that would be strong evidence I should rewrite the whole script in a performant language.
For your use case Jetchisel's suggestion to use bc <<< "$ram > $inacceptableRAM" is probably sufficient, as the Python equivalent is more verbose, but for something more complex Python may be a good choice.

Unix script error while assigning command substitution to array variable [duplicate]

This question already has answers here:
Why does my Bash code fail when I run it with 'sh'?
(2 answers)
Closed 2 years ago.
From the below script I am assigning the command substitution for the array variables.
When I execute the script, the script is throwing error as
myscript.sh: 22: scr4.sh: Syntax error: "(" unexpected
ie while executing the command suite=( echo $Suites ).
I took this script from Advanced Bash scripting Guide pdf.
When I run this command by command in CLI, there is no issue.
What is the solution for this?
#!/bin/bash
Suites="Clubs
Diamonds
Hearts
Spades"
Denominations="2
3
4
5
6
7
8
9
10
Jack
Queen
King
Ace"
suite=( `echo $Suites` )
denomination=( `echo $Denominations` )
num_suites=${#suite[*]}
num_denominations=${#denomination[*]}
echo -n "${denomination[$((RANDOM%num_denominations))]} of "
echo ${suite[$((RANDOM%num_suites))]}
exit 0
You're probably using the wrong interpreter.
$ bash cards.sh
King of Diamonds
$ sh cards.sh
cards.sh: 22: Syntax error: "(" unexpected
POSIX shell does not support arrays. You define an array on line 22 (suite=( `echo $Suites` )) and another on the next code line, then most of your remaining lines refer to those arrays. $RANDOM is also unavailable in POSIX.
Another note, you don't need those subshells. Here's a cleaned up version of your code:
#!/bin/bash
suit=( Clubs Diamonds Hearts Spades )
denomination=( {2..10} Jack Queen King Ace )
echo -n "${denomination[ $(( RANDOM % ${#denomination[#]} )) ]} of "
echo "${suit[ $(( RANDOM % ${#suit[#]} )) ]}"
exit 0
This doesn't launch any subshells. Instead, it's directly defining the arrays. I used {2..10} (another bashism) as shorthand for listing each number. Rather than storing the lengths in variables, I'm invoking them directly in the modulo math, which I've spaced out to make more legible.
I changed ${variable[*]} to ${variable[#]} as well. This doesn't matter in your particular code, but it's generally better to use # since it is better at preserving spacing.
I also corrected the spelling of your suite variable to suit. Feel free to revert if that's actually what you desired (perhaps due to a spoken language difference). It of course doesn't really matter as long as you're consistent; bash doesn't know English 😉

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).

Double parentheses to increment a variable in bash

Suppose I increment a variable in bash. For instance,
> i=0; for f in `ls *.JPG`; do echo $f $i; ((i++)); done
a0.jpg 0
a1.jpg 1
...
Now I wonder why I need those double parentheses to increment i.
The double parentheses construct is a shell feature to support arithmetic operations.
The same construct can also be used for Loops and special numerical constants.
Also, copied from the first link :
# -----------------
# Easter Egg alert!
# -----------------
# Chet Ramey seems to have snuck a bunch of undocumented C-style
#+ constructs into Bash (actually adapted from ksh, pretty much).
# In the Bash docs, Ramey calls (( ... )) shell arithmetic,
#+ but it goes far beyond that.
# Sorry, Chet, the secret is out.
i++ is a perfectly valid file name, and if I have access to your system, I can make that into a command that does something you don't want.
Try creating a file, /bin/i++ with this content:
#!/bin/sh
echo 'Gotcha'
and then chmod +x /bin/i++

Unix Shell for loop based on an argument

OS = Ubuntu
Filename = generate_zips.sh
I'm trying to create a bunch of files (number based on an arugment). However It is not working.
#!/bin/sh
LIMIT=$1
for ((a=1; a <= LIMIT; a++))
do
mkdir zips
cd zips
touch $a
done
but its returning this
esauer#ubuntu:~/repo/filecheck/src/plugins/archive$ ./generate_zips.sh 10
./generate_zips.sh: 4: ./generate_zips.sh: Syntax error: Bad for loop variable
I cant figure out whats wrong...
Your shebang is wrong - you're using a bash feature (the "C-style" for-loop in double parens) but you specified /bin/sh. Change that to /bin/bash.
You can also change your for-loop so that it doesn't use bash's special features, of course (see the other answers). This is somewhat more portable, as not all Unix-like systems have /bin/bash (but pretty much all of them have /bin/sh). However, bash is pretty wide-spread, too (it's the standard shell in most Linux distributions, for example).
Try:
LIMIT=$1
for a in $(seq 1 $LIMIT)
do
mkdir -p zips
touch zips/$a
done
I might do it something like this...
#!/bin/sh
mkdir zips
cd zips
a=1; while [ $a -le $1 ]; do
touch $a
a=$(($a + 1))
done
There are bash-specific ways that are a bit shorter but this will work on any Posix shell.

Resources