this is another question related to importing values from a text file (similar to one of my previous ones), but with added complexity (the more I learn about bash scripting the more challenging it becomes)
The goal: to create an array of Day_.... on each outer loop iteration. I'm trying to do this assuming no knowledge of how many Day_... lists exist in the *.txt file.
The issue: At the moment my inner loop only iterates once (should iterate as the number of elements on Monday. And, also, I'm using my_sub_dom=$( sed 's/=.*//' weekly.txt ) to get the number of lists/arrays in weekly.txt and then filter the ones that contain Day.
Bash script:
#!/bin/bash
source weekly.txt
declare -a my_sub_dom
day=( ${Monday[*]} )
my_sub_dom=$( sed 's/=.*//' weekly.txt ) # to construct a list of the number of of lists in the text file
#echo "${my_sub_dom}"
counter=0
main_counter=0
for i in "${day[#]}"
do
let main_counter=main_counter+1
for j in "${my_sub_dom[#]}"
do
# echo "$j"
if grep -q "Day" "${my_sub_dom}"
then
echo "$j"
sub_array_name="${my_sub_dom[counter]}" # storing the list name
sub_array_content=( ${sub_array_name[*]} )
echo "${sub_array_content}"
else
echo "no"
fi
let counter=counter+1
done
echo "$counter"
counter=0
done
echo "$main_counter"
Text file format:
Day_Mon=( "google" "yahoo" "amazon" )
Day_Tu=( "cnn" "msnbc" "google" )
Day_Wed=( "nytimes" "fidelity" "stackoverflow" )
Monday= ( "one" "two" "three" )
....
Script output:
grep: Day_Mon
Day_Tu
Day_Wed
Monday: No such file or directory
no
1
grep: Day_Mon
Day_Tu
Day_Wed
Monday: No such file or directory
no
1
grep: Day_Mon
Day_Tu
Day_Wed
Monday: No such file or directory
no
1
3
Please let me know if you'd like any other information.... And I really appreciate any input in this matter, I've been trying this for a couple of days now.
Thank you
Given a file weekly.txt containing
Day_Mon=( "google" "yahoo" "amazon" )
Day_Tu=( "cnn" "msnbc" "google" )
Day_Wed=( "nytimes" "fidelity" "stackoverflow" )
Monday=( "one" "two" "three" )
You can loop through each array named Day_-something with
#!/bin/bash
# Set all arrays defined in the file
source weekly.txt
# Get all variables prefixed with "Day_" (bash 4)
for name in "${!Day_#}"
do
echo "The contents of array $name is: "
# Use indirection to expand the array
arrayexpansion="$name[#]"
for value in "${!arrayexpansion}"
do
echo "-- $value"
done
echo
done
This results in:
The contents of array Day_Mon is:
-- google
-- yahoo
-- amazon
The contents of array Day_Tu is:
-- cnn
-- msnbc
-- google
The contents of array Day_Wed is:
-- nytimes
-- fidelity
-- stackoverflow
Related
I know that reading a .csv file can be done simply in bash with this loop:
#!/bin/bash
INPUT=data.cvs
OLDIFS=$IFS
IFS=,
[ ! -f $INPUT ] && { echo "$INPUT file not found"; exit 99; }
while read flname dob ssn tel status
do
echo "Name : $flname"
echo "DOB : $dob"
echo "SSN : $ssn"
echo "Telephone : $tel"
echo "Status : $status"
done < $INPUT
IFS=$OLDIFS
But I want to slightly modify this- I want to make the columns be defined by the programmer in the bash file.
For example:
declare -a columns=("Name", "Surname", "ID", "Gender")
while read columns
do
//now echo everything that has been read
done < $INPUT
So I want to specify the list of variables that should be used as the container to the read CSV data with an array and then access this array inside the while body.
Is there a way to do it?
The key to this solution is the comment before the while statement below. read is a built-in, but it is still a command, and command arguments are expanded by the shell before executing the command. After expansion of ${columns[#]}, the command becomes
read Name Surname ID Gender
Example:
# Don't use commas in between array values (since they become part of the value)
# Values not quoted because valid names don't need quotes, and these
# value must be valid names
declare -a columns=(Name Surname ID Gender)
Then, we can try:
# Read is a command. Arguments are expanded.
# The quotes are unnecessary but it's hard to break habits :)
while read "${columns[#]}"; do
echo Name is "$Name"
# etc
done <<< "John Doe 27 M"
Output:
Name is John
This same approach would work even in a shell without arrays; the column names can just be a space separated list. (Example run in dash, a Posix shell)
$ columns="Name Surname ID Gender"
$ # Here it is vital that $columns not be quoted; we rely on word-splitting
$ while read $columns; do
> echo Name is $Name
> done
John Doe 27 M
Name is John
...
Read the line into an array, then loop through that array and create an associative array that uses the column names.
while read -r line
do
vals=($line)
declare -A colmap
i=0
for col in ${columns[#]}
do
colmap[col]=${vals[$i]}
let i=i+1
done
# do stuff with colmap here
# ...
unset colmap # Clear colmap before next iteration
done < $INPUT
I am trying to create a dictionary program in Bash with the following options : 1. Add a word
2. Update meaning
3. Print dictionary
4. Search a word
5. Search by keyword
For the same, I am creating 2 associative arrays, 1 to store the word - meaning and other to store words-keyword.
The problem is I am not able to store values in the array. Everytime I try to do it, it gives me an error
dict[$word]: bad array subscript
Here is the code for part 1
echo
echo -n "Enter a word : "
read $word
echo
echo -n "Enter it's meaning : "
read $meaning
echo
echo -n "Enter some keywords(with space in between) to describe the word : "
read $keyword
dict[$word]=$meaning
keywords[$word]=$keyword
;;
I also tried inserting the following code to remove new line as suggested in some posts but ended up with same result.
word=`echo $word | grep -s '\n'`
keyword=`echo $keyword | grep -s '\n'`
Have also tried the following way :
dict["$word"]="$meaning"
keywords["$word"]="$keyword"
;;
Output :
dict[$word]: bad array subscript
When reading a variable you preface the variable name with a $ (or wrap in $( and )).
When assigning a value to a variable you do not preface the variable name with a $.
In your example your 3x echo/read sessions are attempting to assign values to your variables, but you've prefaced your variables with $, which means your variables are not getting set as you expect; this in turn could be generating your error because $word is not set/defined (depends on version of bash).
You should be able to see what I mean with the following code snippet:
unset word
echo
echo -n "Enter a word : "
read $word
echo ".${word}."
What do you get as ouput? .. ? .<whatever_you_typed_in>. ?
You may also have a problem with your associative arrays (depending on bash version); as George has mentioned, you should play it safe and explicitly declare your associative arrays.
I would suggest editing your input script like such (remove leading $ on your read variables; explicitly declaring your associative arrays):
echo
echo -n "Enter a word : "
read word
echo
echo -n "Enter it's meaning : "
read meaning
echo
echo -n "Enter some keywords(with space in between) to describe the word : "
read keyword
# print some debug messages:
echo "word=.${word}."
echo "meaning=.${meaning}."
echo "keyword=.${keyword}."
# declare arrays as associative
declare -A dict keywords
# assign values to arrays
dict[$word]=$meaning
keywords[$word]=$keyword
# verify array indexes and values
echo "dict index(es) : ${!dict[#]}"
echo "dict value(s) : ${dict[#]}"
echo "keywords index(es): ${!keywords[#]}"
echo "keywords value(s) : ${keywords[#]}"
In my bash 4.4 , this is not raising any error but is not working correctly either:
$ w="one";m="two";d["$w"]="$m";declare -p d
declare -a d=([0]="two")
It is obvious that bash determines array d as a normal array and not as an associative array.
On the contrary, this one works fine:
$ w="one";m="two";declare -A d;d["$w"]="$m";declare -p d
declare -A d=([one]="two" )
According to bash manual, you need first to declare -A an array in order to be used as an associative one.
Don't know where to ask for help so trying here.
I'm creating a bash menu script with some operations and reading lots of bash tutorials but think my brain is starting to melt with all different syntax and ways to do it, can't fully wrap my head around bash/sh. End script will run on OSX for an art team.
What this is
A script to upload/download files with rsync.
The script that will grab latest 'config/menu' from remote server. That file menu.txt will create an menu that lists project and when selected you get the option to download / upload.
Issue
Where I'm stuck is how to handle arrays to menus. Tried 2d arrays with no luck so now it's split into 3 arrays to hold the values I need. However when trying to display the menus I can't get it to work correctly. Look at the bottom for how to test, what is shows and what it should show.
more info: create_menus function
This function parse the menu.txt to build an array that is used when showing a menu,
Project Title5, source1dir, destination1dir
Project Title6, source2dir, destination2dir
Project Title7, source3dir, destination3dir
Instead of gettin selection 1 'Project Title5' a menu will display
1) Project
2) Title5Project
3) Title6Project
4) Title7Quit
Script:
function create_menus() {
#operations for project
MENU_OPERATIONS=(
"Get latest from remote"
"Show changes from remote"
"Send latest from me to remote"
"Show changes from me to remote"
"Return to main menu"
)
#projects to choose from, load from textfile
declare -a t; declare -a s; declare -a d;
while IFS= read -r line; do
IFS=',' read -ra obj <<< "$line"
#TODO 2d array nicer than 3 arrays!
eval "t+=\"${obj[0]}\""
eval "s+=\"${obj[1]}\""
eval "d+=\"${obj[2]}\""
done <$FILE_MENU
t+="Quit" #add quit
MENU_MAIN=($t)
PROJECT_SOURCE=($s)
PROJECT_TARGET=($d)
}
then to show the main menu main_menu "${MENU_MAIN[#]}"
function main_menu
{
#clear
#header
PS3="Select project: "
select option; do # in "$#" is the default
if [ "$REPLY" -eq "$#" ];
then
echo "Exiting..."
break;
elif [ 1 -le "$REPLY" ] && [ "$REPLY" -le $(($#-1)) ];
then
# $REPLY = index
# $option = text
echo "You selected $option which is option $REPLY"
SELETED_PROJECT_TITLE=${MENU_MAIN[$REPLY]}
SELETED_PROJECT_SOURCE=${PROJECT_SOURCE[$REPLY]}
SELETED_PROJECT_TARGET=${PROJECT_TARGET[$REPLY]}
echo "Sel title $SELETED_PROJECT_TITLE"
echo "Sel source $SELETED_PROJECT_SOURCE"
echo "Sel target $SELETED_PROJECT_TARGET"
project_menu "${MENU_OPERATIONS[#]}" "$SELETED_PROJECT_TITLE" "$SELETED_PROJECT_SOURCE" "$SELETED_PROJECT_TARGET"
break;
else
echo "Incorrect Input: Select a number 1-$#"
fi
done
}
Here's full code
https://github.com/fbacker/BigFileProjectsSync/blob/master/app.sh
ADDED MORE DESCRIPTION
To test:
git clone https://github.com/fbacker/BigFileProjectsSync.git
cd BigFileProjectsSync/
./app.sh
What happens:
Shows a menu with options:
1) Project
2) Title5Project
3) Title6Project
4) Title7Quit
Should happen:
Shows a menu with options:
1) Project Title5
2) Project Title6
3) Project Title7
4) Quit
app.sh > function create_menus() > This should create a menu based on the menu.txt file.
menu.txt one line is a project: first value is project name, second value is source directory and third is target directory.
Here's a fixed version of your create_menus() function, which should do the trick:
function create_menus() {
#operations for project
# MENU_OPERATIONS=( # ... OMITTED FOR BREVITY
#projects to choose from
local -a titles sources destinations
local title source destination
while IFS='|' read -r title source destination; do
titles+=( "$title" )
sources+=( "$source" )
destinations+=( "$destination" )
done < <(sed 's/, /|/g' "$FILE_MENU")
# Copy to global arrays
MENU_MAIN+=( "${titles[#]}" )
PROJECT_SOURCE+=( "${sources[#]}" )
PROJECT_TARGET+=( "${destinations[#]}" )
MENU_MAIN+=( "Quit" ) #add quit
}
There were 2 crucial problems with your approach (I'll assume an array variable $arr below):
In order to append a new element to an array, the new element must itself be specified as an array too; i.e., it must be enclosed in (...):
arr+=( "$newElement" ) - OK: value is appended as new element
arr+=$newElement - BROKEN: String-appends the value of $newElement to $arr's first element(!), without adding a new one.
arr=( 1 2 ); arr+=3; declare -p arr -> declare -a arr='([0]="13" [1]="2")'
You can't copy a whole array with arrCopy=( $arr ) - all that does is to create a single-item array containing only $arr's first element. To refer to an array as a whole, you must use "${arr[#]}" (enclosing in "..." ensures that no word-splitting is applied):
arrCopy=( "${arr[#]}" ) - OK
arrCopy=( $arr ) - BROKEN - only copies 1st element
Also note that it's better not to use all-uppercase shell-variable names in order to avoid conflicts with environment variables and special shell variables.
I want a script bash/sh/ksh which compare a lot of variables, maybe in an array, and tell me if variable is empty or not.
I think something like this, it but doesn't work.
ARRAY=(
bash="yes"
cash=""
trash="no"
empty=""
)
for var in "${ARRAY[#]}"; do
if [ "$var" == "$empty" ]
then
echo "$var is empty"
else
echo "$var is not empty"
fi
done
I want an output like this
bash is not empty
cash is empty...
If you're willing to limit your runtime environment to a recent version of bash (or modify the code to support ksh93's equivalent syntax),
#!/bin/bash
# ^^^^ -- specifically, bash 4.0 or newer
declare -A array # associative arrays need to be declared!
array=( [bash]="yes" [cash]="" [trash]="no" [empty]="" )
for idx in "${!array[#]}"; do
if [[ ${array[$idx]} ]]; then
echo "$idx is not empty"
else
echo "$idx is empty"
fi
done
To iterate over keys in an array, as opposed to values, the syntax is "${!array[#]}", as opposed to "${array[#]}"; if you merely iterate over the values, you don't know the name of the one currently being evaluated.
Alternately, let's say we aren't going to use an array at all; another way to set a namespace for variables you intend to be able to treat in a similar manner is by prefixing them:
#!/bin/bash
val_bash=yes
val_cash=
val_trash=no
val_empty=
for var in "${!val_#}"; do
if [[ ${!var} ]]; then
echo "${var#val_} is not empty"
else
echo "${var#val_} is empty"
fi
done
This works (on bash 3.x as well) because "${!prefix#}" expands to the list of variable names starting with prefix, and "${!varname}" expands to the contents of a variable whose name is itself stored in the variable varname.
Iterate over the array elements, and inside the loop for read set IFS as = to get variable and it's value in two separate variables, then check if the value is empty:
for i in "${array[#]}"; do
IFS== read var value <<<"$i"
if [ -z "$value" ]; then
echo "$var is empty"
else
echo "$var is not empty"
fi
done
Outputs:
bash is not empty
cash is empty
trash is not empty
empty is empty
It's a simple thing I'm sure, just with the right combination.
I have a file with a list of prepared arrays. I modify them using sed reading them all into a variable. I then need to set them as part of a script.
Original reference file:
var1=( "1" "%%" )
var2=( "2" "%%" )
I do my stuff with it so it becomes:
var1=( "1" "3" )
var2=( "2" "4" )
...and the list of modified arrays is all stored in a variable. I need to then read that list of arrays into a script.
Try eval:
$ x="a=asdf"; eval $x ; echo $a
outputs: asdf