Output of command in Bash script to Drop-down box? - bash

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"

Related

Pausing cURL to Clear Output and Resuming

I hope this is not off-topic here. This is the background:
My friend and I are trying to solve this online puzzle where you basically have to enter a 16 digit combination; if wrong, the site'll display "incorrect" and, if right, some other message we don't know about. We have the first eight numbers so we figured it would be feasible attempting to brute-force it using curl and saving to output to check.
We've implemented it like this:
curl http://www.game.com/checkCombo.php?num1=[0-9]&num2=[0-9]...&num8=[0-9] >> output.txt
As you can see, we're basically running through all possible combinations and appending each individual output to the "output.txt" file.
The way we check whether we've gotten the right combination, since we don't know what it'll say then, is by searching the number of "incorrects"; if when we open the file we've gone through 1000 combinations and there are 1000 matches for "incorrect" then we know we haven't hit the right one yet. If when we open the file we've gone through 2000 combos and there are only 1999 matches for "incorrect", we know we've hit the right combo and we just search for it.
However, since we're saving 10^8=100,000,000 individual outputs, we're running out of space on the computer.
We tried pausing the cURL process with "ctrl+z" and checking the file to see whether we've hit the right combo. If not, we clear the "output.txt" file, save it, and resume the cURL process with "fg %#".
The problem is that when we resume cURL, after we've cleared the text file, the file remains empty: the output of cURL won't save any more onto the file.
I'd like to know whether there's a way of pausing cURL to check the file, clearing the file, and resuming curl while continuing to save the remaining outputs.
If you have other ideas on how we might go about the combination, I'd also appreciate hearing about them. Thank you.
A simplified case will show different things to get started. Start testing without curl until you have a working solution.
I have written a funtction checkcombo that needs 3 parameters and has a hidden solution:
checkcombo () {
digit=$1$2$3
if [[ $digit = 412 ]]; then
echo "How did you find me?"
else
echo incorrect
fi
}
Try this function with
checkcombo 4 1 0
checkcombo 4 1 1
checkcombo 4 1 2
Nice, now we want bruteforce. Perhaps the best solution is one loop until 999 and split each number in 3 digits, but I want to show the nested loops.
for i in {0..9}; do
for i2 in {0..9}; do
for i3 in {0..9}; do
checkcombo $i $i2 $i3 | grep -q "incorrect" || { echo The answer is $i $i2 $i3 && break 3; }
done
done
done
The line with checkcombo can be hard to read. It is a short way of saying
if [ $(checkcombo $i $i2 $i3 | grep "incorrect" | wc -l) -eq 0 ]; then
echo "The answer is $i $i2 $i3"
# and stop testing with break 3
fi

Variable disappear after quitting the while loop

I know this question has been answered so many times. However, I still have some points need to be clarified. First let me paste my code snippet:
1 #!/bin/bash
2 declare -a test
3 declare -i counter
4
5 while read x;
6 do
7 test[counter]=$x
9 ((counter++))
10 done < reg.txt
11 echo "---------------------"
12 echo ${test[0]}
13 echo ${test[1]}
And the data in reg.txt is
1.1.1.1
2.2.5.6
45.25.12.45
1.1.2.3
1.1.3.4
I know that to put data in array test properly, I have to use '<' to turn file "reg.txt" into input data. However, how am I supposed to pick out ip address contains "1.1".
At line 10, I tried different things such as:
done < reg.txt|grep "1.1" #Using this way makes the 'test' array empty.
Or this:
done < <(reg.txt | grep "1.1")
The grammar is incorrect. (A lot of people suggest this and I don't know why).
In summary, I mean, is there a way to re-construct file before being read by while loop?
Using this syntax:
done < reg.txt|grep "1.1"
doesn't do what you want it to do; instead, it applies the grep command to the output of the while loop.
The test array is does get populated with 5 values, but those values aren't remembered after the while loop completes - as explained in the answers to this question: Modifying a variable inside while loop is not remembered
What you're looking for is this:
done < <(cat reg.txt | grep "1\.1")
Note that the part within the parenthesis is a pipeline, and it needs to be a valid bash command. (You were missing the "cat" command.) You can test that part separately and verify that it selects the input data that you want.

Read range of numbers into a for loop

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

Reading Column and Find Median (Bash)

I want to find the median for each column, however it doesn't work like what I want.
1 2 3
3 2 1
2 1 5
I'm expecting for
2 2 3
for the result, however turns out it just give sum error and some "sum" of the column. Below is a snippet of the code for "median in column"
while read -r line; do
read -a array <<< "$line"
for i in "${!array[#]}"
do
column[${i}]=${array[$i]}
((length[${i}]++))
result=${column[*]} | sort -n
done < file
for i in ${!column[#]}
do
#some median calculation.....
Notes: I want to practice bash, that's why I hard-coded using bash.
I really appreciate if someone could help me, especially in BASH. Thank you.
Bash is really not suitable for low-level text processing like this: the read command does a system call for each character that it reads, which means that it's slow, and it's a CPU hog. It's ok for processing interactive input, but using it for general text processing is madness. It would be much better to use awk (Python, Perl, etc) for this.
As an exercise in learning about Bash I guess it's ok, but please try to avoid using read for bulk text processing in real programs. For further information, please see Why is using a shell loop to process text considered bad practice? on the Unix & Linux Stack Exchange site, especially the answer written by
Stéphane Chazelas (the discoverer of the Shellshock Bash bug).
Anyway, to get back to your question... :)
Most of your code is ok, but
result=${column[*]} | sort -n
doesn't do what you want it to.
Here's one way to get the column medians in pure Bash:
#!/usr/bin/env bash
# Find medians of columns of numeric data
# See http://stackoverflow.com/q/33095764/4014959
# Written by PM 2Ring 2015.10.13
fname=$1
echo "input data:"
cat "$fname"
echo
#Read rows, saving into columns
numrows=1
while read -r -a array; do
((numrows++))
for i in "${!array[#]}"; do
#Separate column items with a newline
column[i]+="${array[i]}"$'\n'
done
done < "$fname"
#Calculate line number of middle value; which must be 1-based to use as `head`
#argument, and must compensate for extra newline added by 'here' string, `<<<`
midrow=$((1+numrows/2))
echo "midrow: $midrow"
#Get median of each column
result=''
for i in "${!column[#]}"; do
median=$(sort -n <<<"${column[i]}" | head -n "$midrow" | tail -n 1)
result+="$median "
done
echo "result: $result"
output
input data:
1 2 3
3 2 1
2 1 5
midrow: 3
result: 2 2 3

Use first 3 characters of a filename as a variable in shell script

this is my first post so hopefully I will make my question clear.
I am new to shell scripts and my task with this one is to add a new value to every line of a csv file. The value that needs added is based on the first 3 digits of the filename.
I bit of background. The csv files I am receiving are eventually being loaded into partitioned oracle tables. The start of the file name (e.g. BATTESTFILE.txt) contains the partitioned site so I need to write a script that takes the first 3 characters of the filename (in this example BAT) and add this to the end of each line of the file.
The closest I have got so far is when I stripped the code to the bare basics of what I need to do:
build_files()
{
OLDFILE=${filename[#]}.txt
NEWFILE=${filename[#]}.NEW.txt
ABSOLUTE='path/scripts/'
FULLOLD=$ABSOLUTE$OLDFILE
FULLNEW=$ABSOLUTE$NEWFILE
sed -e s/$/",${j}"/ "${FULLOLD}" > "${FULLNEW}"
}
set -A site 'BAT'
set -A filename 'BATTESTFILE'
for j in ${site[#]}; do
for i in ${filename[#]}; do
build_files ${j}
done
done
Here I have set up an array site as there will be 6 'sites' and this will make it easy to add additionals sits to the code as the files come through to me. The same is to be siad for the filename array.
This codes works, but it isn't as automated as I need. One of my most recent attempts has been below:
build_files()
{
OLDFILE=${filename[#]}.txt
NEWFILE=${filename[#]}.NEW.txt
ABSOLUTE='/app/dss/dsssis/sis/scripts/'
FULLOLD=$ABSOLUTE$OLDFILE
FULLNEW=$ABSOLUTE$NEWFILE
sed -e s/$/",${j}"/ "${FULLOLD}" > "${FULLNEW}"
}
set -A site 'BAT'
set -A filename 'BATTESTFILE'
for j in ${site[#]}; do
for i in ${filename[#]}; do
trust=echo "$filename" | cut -c1-3
echo "$trust"
if ["$trust" = 'BAT']; then
${j} = 'BAT'
fi
build_files ${j}
done
done
I found the code trust=echo "$filename" | cut -c1-3 through another question on StackOverflow as I was researching, but it doesn't seem to work for me. I added in the echo to test what trust was holding, but it was empty.
I am getting 2 errors back:
Line 17 - BATTESTFILE: not found
Line 19 - test: ] missing
Sorry for the long winded questions. Hopefully It contains helpful info and shows the steps I have taken. Any questions, comment away. Any help or guidance is very much appreciated. Thanks.
When you are new with shells, try avoiding arrays.
In an if statement use spaces before and after the [ and ] characters.
Get used to surrounding your shell variables with {} like ${trust}
I do not know how you fill your array, when the array is hardcoded, try te replace with
SITE=file1
SITE="${SITE} file2"
And you must tell unix you want to have the rightside eveluated with $(..) (better than backtics):
trust=$(echo "${filename}" | cut -c1-3)
Some guidelines and syntax help can be found at Google
Just use shell parameter expansion:
$ var=abcdefg
$ echo "${var:0:3}"
abc
Assuming you're using a reasonably capable shell like bash or ksh, for example
Just in case it is useful for anyone else now or in the future, I got my code to work as desired by using the below. Thanks Walter A below for his answer to my main problem of getting the first 3 characters from the filename and using them as a variable.
This gave me the desired output of taking the first 3 characters of the filename, and adding them to the end of each line in my csv file.
## Get the current Directory and file name, create a new file name
build_files()
{
OLDFILE=${i}.txt
NEWFILE=${i}.NEW.txt
ABSOLUTE='/app/dss/dsssis/sis/scripts/'
FULLOLD=$ABSOLUTE$OLDFILE
FULLNEW=$ABSOLUTE$NEWFILE
## Take the 3 characters from the filename and
## add them onto the end of each line in the csv file.
sed -e s/$/";${j}"/ "${FULLOLD}" > "${FULLNEW}"
}
## Loop to take the first 3 characters from the file names held in
## an array to be added into the new file above
set -A filename 'BATTESTFILE'
for i in ${filename[#]}; do
trust=$(echo "${i}" | cut -c1-3)
echo "${trust}"
j="${trust}"
echo "${i} ${j}"
build_files ${i} ${j}
done
Hope this is useful for someone else.

Resources