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 😉
Related
So, I am building a bash script which iterates through folders named by numbers from 1 to 9. The script depends on getting the folder names by user input. My intention is to use a for loop using read input to get a folder name or a range of folder names and then do some stuff.
Example:
Let's assume I want to make a backup with rsync -a of a certain range of folders. Usually I would do:
for p in {1..7}; do
rsync -a $p/* backup.$p
done
The above would recursively backup all content in the directories 1 2 3 4 5 6 and 7 and put them into folders named as 'backup.{index-number}'. It wouldn't catch folders/files with a leading . but that is not important right now.
Now I have a similar loop in an interactive bash script. I am using select and case statements for this task. One of the options in case is this loop and it shall somehow get a range of numbers from user input. This now becomes a problem.
Problem:
If I use read to get the range then it fails when using {1..7} as input. The input is taken literally and the output is just:
{1..7}
I really would like to know why this happens. Let me use a more descriptive example with a simple echo command.
var={1..7} # fails and just outputs {1..7}
for p in $var; do echo $p;done
read var # Same result as above. Just outputs {1..7}
for p in $var; do echo $p;done
for p in {1..7}; do echo $p;done # works fine and outputs the numbers 1-7 seperated with a newline.
I've found a workaround by storing the numbers in an array. The user can then input folder names seperated by a space character like this: 1 2 3 4 5 6 7
read -a var # In this case the output is similar to the 3rd loop above
for p in ${var[#]}; do echo $p; done
This could be a way to go but when backing up 40 folders ranging from 1-40 then adding all the numbers one-by-one completely makes my script redundant. One could find a solution to one of the millennium problems in the same time.
Is there any way to read a range of numbers like {1..9} or could there be another way to get input from terminal into the script so I can iterate through the range within a for-loop?
This sounds like a question for google but I am obviously using the wrong patterns to get a useful answer. Most of similar looking issues on SO refer to brace and parameter expansion issues but this is not exactly the problem I have. However, to me it feels like the answer to this problem is going in a similar direction. I fail to understand why when a for-loop for assigning {1..7} to a variable works but doing the same like var={1..7} doesn't. Plz help -.-
EDIT: My bash version:
$ echo $BASH_VERSION
4.2.25(1)-release
EDIT2: The versatility of a brace expansion is very important to me. A possible solution should include the ability to define as many ranges as possible. Like I would like to be able to choose between backing up just 1 folder or a fixed range between f.ex 4-22 and even multiple options like folders 1,2,5,6-7
Brace expansion is not performed on the right-hand side of a variable, or on parameter expansion. Use a C-style for loop, with the user inputing the upper end of the range if necessary.
read upper
for ((i=1; i<=$upper; i++)); do
To input both a lower and upper bound separated by whitespace
read lower upper
for (i=$lower; i <= $upper; i++)); do
For an arbitrary set of values, just push the burden to the user to generate the appropriate list; don't try to implement your own parser to process something like 1,2,20-22:
while read p; do
rsync -a $p/* backup.$p
done
The input is one value per line, such as
1
2
20
21
22
Even if the user is using the shell, they can call your script with something like
printf '%s\n' 1 2 20..22 | backup.sh
It's easier for the user to generate the list than it is for you to safely parse a string describing the list.
The evil eval
$ var={1..7}
$ for i in $(eval echo $var); do echo $i; done
this also works,
$ var="1 2 {5..9}"
$ for i in $(eval echo $var); do echo $i; done
1
2
5
6
7
8
9
evil eval was a joke, that is, as long as you know what you're evaluating.
Or, with awk
$ echo "1 2 5-9 22-25" |
awk -v RS=' ' '/-/{split($0,a,"-"); while(a[1]<=a[2]) print a[1]++; next}1'
1
2
5
6
7
8
9
22
23
24
25
This question already has answers here:
Bash: evaluate a mathematical term?
(9 answers)
Closed 6 years ago.
When I try to calculate a maths problem such as 1 + 1 using the Ubuntu console like this:
user#servername:~$ 1 + 1
Ubuntu thinks the first 1 is a command and I get this error:
1: command not found
I then tried to use echo to evaluate the string (with and without quotes) like this:
user#servername:~$ echo 1 + 1
user#servername:~$ echo "1 + 1"
Unfortunatly both output 1 + 1 and not 2.
It would also be greatly appreciated to include a explanation as to why echo does not evaluate the specified string before outputting it?
Also is there is a built-in command that evaluates a string before outputting it (maybe something that behaves like eval in Python)?
Thank you for the help in advance.
The one I usually use is
bc<<<2+2
You can make that easier to type with an alias:
$ alias x='bc<<<'
$ x 53*(27-23)
212
If you put whitespace in the expression you'll need to quote it. Also if the expression starts with an open parenthesis (as opposed to having one in the middle).
$ echo $((1+1))
2
$ echo "1+1" | bc
2
$ awk 'BEGIN{print 1+1}'
2
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!
First off, I appreciate any and all help in answering this question.
I have a command in a bash script that will output the following:
255 254 253 252 ... 7 6 5 4 3 2 1
It is a specific list of numbers, beginning with the largest (which is what I would like), then going to the smallest. The dataset is space-delimited. The output above (except including all numbers), is what you would see if you ran this command in the terminal on a linux machine, or through a bash script.
I have configured my apache2 server to allow for cgi/bash through the cgi-bin directory. When I run this command in a bash file from the web, I get the expected output.
What I'm looking for is for a way to be able to put these numbers each as a separate entry in a drop-down box for selection, meaning the user can select one point of data (254, for example) from the drop down menu.
I'm not sure what I'm doing with this, so any help would be appreciated. I'm not sure if I need to convert the data into an array, or what. The drop down menu can be on the same page of the bash script, but wherever it is, it has to update it's list of numbers from the command every time it is run.
Thank you for your help.
I've always found this site useful when fiddling with shell scripts: http://tldp.org/LDP/abs/html/
you'll have to get your output into an array using some sort of string manipulation using the spaces as delimiters, then loop over that to build some html output - so the return value will basically just output your select box on the page where you execute your cgi/bash script.
-sean
Repeating the answer (since the original question was marked as duplicate):
you can write a bash for loop to do everything. This just prints out the elements:
for i in `seq 1 "${#x[*]}"`; do
echo "|${x[i]} |"
done
To get the alignment correct, you need to figure out the max length (one loop) and then print out the terms:
# w will be the length
w=0
for i in `seq 1 "${#x[*]}"`; do
if [ $w -lt ${#x[$i]} ]; then w=${#x[$i]}; fi
done
for i in `seq 1 $((w+2))`; do printf "%s" "-"; done
printf "\n"
for i in `seq 1 "${#x[*]}"`; do
printf "|%-$ws |\n" ${#x[$i]}
done
for i in `seq 1 $((w+2))`; do printf "%s" "-"; done
printf "\n"
This question already has answers here:
Dynamic variable names in Bash
(19 answers)
Closed 6 years ago.
I apologise for the pretty terrible title - and the poor quality post - but what I basically want to do is this:
for I in 1 2 3 4
echo $VAR$I # echo the contents of $VAR1, $VAR2, $VAR3, etc.
Obviously the above does not work - it will (I think) try and echo the variable called $VAR$I Is this possible in Bash?
Yes, but don't do that. Use an array instead.
If you still insist on doing it that way...
$ foo1=123
$ bar=foo1
$ echo "${!bar}"
123
for I in 1 2 3 4 5; do
TMP="VAR$I"
echo ${!TMP}
done
I have a general rule that if I need indirect access to variables (ditto arrays), then it is time to convert the script from shell into Perl/Python/etc. Advanced coding in shell though possible quickly becomes a mess.
You should think about using bash arrays for this sort of work:
pax> set arr=(9 8 7 6)
pax> set idx=2
pax> echo ${arr[!idx]}
7
For the case where you don't want to refactor your variables into arrays...
One way...
$ for I in 1 2 3 4; do TEMP=VAR$I ; echo ${!TEMP} ; done
Another way...
$ for I in 1 2 3 4; do eval echo \$$(eval echo VAR$I) ; done
I haven't found a simpler way that works. For example, this does not work...
$ for I in 1 2 3 4; do echo ${!VAR$I} ; done
bash: ${!VAR$I}: bad substitution
for I in {1..5}; do
echo $((VAR$I))
done
Yes. See Advanced Bash-Scripting Guide
This definately looks like an array type of situation. Here's a link that has a very nice discussion (with many examples) of how to use arrays in bash: Arrays