Select command separates the options with spaces listed from a file [duplicate] - bash

This question already has answers here:
Creating an array from a text file in Bash
(7 answers)
How can I write a Bash Script to display a menu, accept user input and display data?
(3 answers)
Closed 4 months ago.
How can I display a selection menu for a user with options I have stored on individual lines of a text file?
For example, my text file (ingestion.txt) looks like this.
SOUP
FTS/CTS
JDBC
NEW
And I want the user to see this
Please select an option by typing in the corresponding number
1) SOUP
2) FTS/CTS
3) JDBC
4) NEW
And then if they didn't type a valid number they would be prompted again.

#!/bin/bash
unset option menu ERROR # prevent inheriting values from the shell
declare -a menu # create an array called $menu
menu[0]="" # set and ignore index zero so we can count from 1
# read menu file line-by-line, save as $line
while IFS= read -r line; do
menu[${#menu[#]}]="$line" # push $line onto $menu[]
done < ingestion.txt
# function to show the menu
menu() {
echo "Please select an option by typing in the corresponding number"
echo ""
for (( i=1; i<${#menu[#]}; i++ )); do
echo "$i) ${menu[$i]}"
done
echo ""
}
# initial menu
menu
read option
# loop until given a number with an associated menu item
while ! [ "$option" -gt 0 ] 2>/dev/null || [ -z "${menu[$option]}" ]; do
echo "No such option '$option'" >&2 # output this to standard error
menu
read option
done
echo "You said '$option' which is '${menu[$option]}'"
This reads through ingestion.txt line by line, then pushes the contents of that line into the $menu array. ${#variable} gives you the length of $variable. When given the entirety of an array ("${array[#]}" is akin to "$#"), such as ${#array[#]}, you get the number of elements in that array. Because bash is zero-indexed, that number is the first available item you can add to, starting with zero given the newly defined (and therefore empty) array.
The menu() function iterates through the menu items and displays them on the screen.
We then loop until we get a valid input. Since bash will interpret a non-number as zero, we first determine if it is actually a (natural) number, discarding any error that would come from non-numbers, then we actually ensure the index exists. If it's not the first iteration, we complain about the invalid input given before. Display the menu, then read user input into $option.
Upon a valid option index, the loop stops and the code then gives you back the index and its corresponding value.

Related

Unix looping from text file

I have 2 text files. I want to loop in the first file to get a list, then using that list, loop from the second file to search for matching fields.
The first loop was fine, but when the second loop comes in, the variable $CLIENT_ABBREV cannot be read in the second loop, it's reading as blank. Output looks like does not match DOG where there's a blank before does.
while IFS=',' read CLIENT_ID NAME SERVER_NAME CLIENT_ABBREV
do
echo "\n------------"
echo Configuration in effect for this run
echo CLIENT_ID=$CLIENT_ID
echo NAME=$NAME
echo SERVER_NAME=$SERVER_NAME
echo CLIENT_ABBREV=$CLIENT_ABBREV
while IFS=',' read JOB_NAME CLIENT_ABBREV_FROMCOMMAND JOBTYPE JOBVER
do
if [ "$CLIENT_ABBREV" == "$CLIENT_ABBREV_FROMCOMMAND" ]; then
# do something
else
echo $CLIENT_ABBREV does not match $CLIENT_ABBREV_FROMCOMMAND
done <"$COMMAND_LIST"
done <"$CLIENT_LIST"
Is there a file with the name COMMAND_LIST ?
Or, actually do you want to use $COMMAND_LIST instead of COMMAND_LIST ?

Why is my for loop only storing all desired items as one element?

I am trying to figure out why all items are being stored as one element:
filedates=($dirPath/*.csv)
filebasenames=()
filedates2=()
for file in ${filedates[#]}; do
filebasenames+="${file##*/} "
done
for i in ${filebasenames[#]}; do
filedates2+="$(echo $i | cut -c6-13) "
done
for i in ${filedates2[#]}; do
echo $i
done
echo test here ${filebasenames[0]}
echo test here ${filebasenames[1]}
Im confused because the third for loop prints each element in a new line so I assumed that there is more than one element in the array but when I echo the "test here" line it shows me all the elements in one line indicating that there is only one large string. I verify that with the second echo test here 2 line
You aren't appending to the array; you are only appending to the first element of the array. Appending to an array requires parentheses. (Notice, too, that I've dropped the space from the new array element.)
for file in ${filedates[#]}; do
filebasenames+=("${file##*/}")
done
That said, you don't need a loop at all; you can apply the ## to all the elements of the array in one operation.
filebasenames=("${filedates[#]##*/}")
The other array is probably still best populated using a loop.
for i in "${filebasenames[#]}"; do
filedates2+=("$(echo "$i" | cut -c6-13)")
done

How to get output values in bash array by calling other program from bash?

I am stuck with a peculiar situation, where in from python I am printing two strings one by one and reading it in bash script (which calls the python code piece)
I am expecting array size to be 2, but somehow, bash considers spaces also as a element separator and return me size of 3.
Example scripts
multi_line_return.py file has following content
print("foo bar")
print(5)
multi_line_call.sh has following content
#!/bin/bash
PYTHON_EXE="ABSOLUTE_PATH TO PYTHON EXECUTABLE IN LINUX"
CURR_DIR=$(cd $(dirname ${BASH_SOURCE[0]}) && pwd)/
array=()
while read line ; do
array+=($line)
done < <(${PYTHON_EXE} ${CURR_DIR}multi_line_return.py)
echo "array length --> ${#array[#]}"
echo "each variable in new line"
for i in "${array[#]}"
do
printf $i
printf "\n"
done
Now keep both of the above file in same directory and make following call to see result.
bash multi_line_call.sh
As you can see in result,
I am getting
array length = 3
1.foo, 2.bar & 3. 5
The expectation is
One complete line of python output (stdout) as one element of bash array
array length = 2
1. foo bar & 2. 5
Put quotes around $line to prevent it from being split:
array+=("$line")
You can also do it without a loop using readarray:
readarray array < <(${PYTHON_EXE} ${CURR_DIR}multi_line_return.py)

How can I create a Bash script that creates multiple files with text, excluding one?

I need to create Bash script that generates text files named file001.txt through file050.txt
Of those files, all should have this text inserted "This if file number xxx" (where xxx is the assigned file number), except for file007.txt, which needs to me empty.
This is what I have so far..
#!/bin/bash
touch {001..050}.txt
for f in {001..050}
do
echo This is file number > "$f.txt"
done
Not sure where to go from here. Any help would be very appreciated.
#!/bin/bash
for f in {001..050}
do
if [[ ${f} == "007" ]]
then
# creates empty file
touch "${f}.txt"
else
# creates + inserts text into file
echo "some text/file" > "${f}.txt"
fi
done
The continue statement can be used to skip an iteration of a loop and go on to the next -- though since you actually do want to take an operation on file 7 (creating it), it makes just as much sense to have a conditional:
for (( i=1; i<50; i++ )); do
printf -v filename '%03d.txt' "$i"
if (( i == 7 )); then
# create file if it doesn't exist, truncate if it does
>"$filename"
else
echo "This is file number $i" >"$filename"
fi
done
A few words about the specific implementation decisions here:
Using touch file is much slower than > file (since it starts an external command), and doesn't truncate (so if the file already exists it will retain its contents); your textual description of the problem indicates that you want 007.txt to be empty, making truncation appropriate.
Using a C-style for loop, ie. for ((i=0; i<50; i++)), means you can use a variable for the maximum number; ie. for ((i=0; i<max; i++)). You can't do {001..$max}, by contrast. However, this does need meaning to add zero-padding in a separate step -- hence the printf.
Of course, you can costumize the files' name and the text, the key thing is the ${i}. I tried to be clear, but let us know if you don't understand something.
#!/bin/bash
# Looping through 001 to 050
for i in {001..050}
do
if [ ${i} == 007 ]
then
# Create an empty file if the "i" is 007
echo > "file${i}.txt"
else
# Else create a file ("file012.txt" for example)
# with the text "This is file number 012"
echo "This is file number ${i}" > "file${i}.txt"
fi
done

Choose more than one option in script shell?

I want to create a menu with a script with multi choice .
like :
1)
2)
3)
4)
5)
I can choose 1 , and 3 , and 5 in the same time .
bash's select compound command doesn't directly support multiple choices, but you can still base your solution on it, taking advantage of the fact that whatever the user enters is recorded in the special $REPLY variable:
#!/usr/bin/env bash
choices=( 'one' 'two' 'three' 'four' 'five' ) # sample choices
select dummy in "${choices[#]}"; do # present numbered choices to user
# Parse ,-separated numbers entered into an array.
# Variable $REPLY contains whatever the user entered.
IFS=', ' read -ra selChoices <<<"$REPLY"
# Loop over all numbers entered.
for choice in "${selChoices[#]}"; do
# Validate the number entered.
(( choice >= 1 && choice <= ${#choices[#]} )) || { echo "Invalid choice: $choice. Try again." >&2; continue 2; }
# If valid, echo the choice and its number.
echo "Choice #$(( ++i )): ${choices[choice-1]} ($choice)"
done
# All choices are valid, exit the prompt.
break
done
echo "Done."
As for how the select command normally works, with a single selection:
Run man bash and look under the heading 'Compound Commands'
For an annotated example, see this answer.
This answer implements custom logic as follows:
The designated target variable of the select command, dummy, is ignored, and the $REPLY variable is used instead, because Bash sets it to whatever the user entered (unvalidated).
IFS=', ' read -ra selChoices <<<"$REPLY" tokenizes the user-entered value:
It is fed via a here-string (<<<) to the read command
using instance of commas and space (,<space>) as the [Internal] Field Separator (IFS=...)
Note that, as a side effect, the user could use spaces only to separate their choices.
and the resulting tokens are stored as elements of array (-a) selChoices; -r simply turns off interpretation of \ chars. in the input
for choice in "${selChoices[#]}"; do loops over all tokens, i.e., the individual numbers the user chose.
(( choice >= 1 && choice <= ${#choices[#]} )) || { echo "Invalid choice: $choice. Try again." >&2; continue 2; } ensures that each token is valid, i.e., that it is a number between 1 and the count of choices presented.
echo "Choice #$(( ++i )): ${choices[choice-1]} ($choice)" outputs each choice and choice number
prefixed with a running index (i), which is incremented (++i) using an arithmetic expansion ($((...))) - since a variable defaults to 0 in an arithmetic context, the first index output will be 1;
followed by ${choices[choice-1]}, i.e., the choice string indicated by the number entered, decremented by 1, because Bash arrays are 0-based; note how choice needs no $ prefix in the array subscript, because a subscript is evaluated in an arithmetic context (as if inside $(( ... ))), as above.
terminated with ($choice), the chosen number in parentheses.
break is needed to exit the prompt; by default, select will keep prompting.

Resources