Bash if statement using wmctrl and xdotool - arrays

What I'm trying to accomplish:
Making an array of active opened windows
uses a combination of wmctrl & xdotool
if the window focus changes then I add that window to the top of the
array
then delete the duplicate window in position [>=1] in the
array.
My problem is that I cannot seem to correctly deal with checking between the wmctrl window id that is open and the xdotool focus window (one is an integer and one is a hexdecimal). My if statement needs work, but I am having a problem finding out how to correctly check my xdotool window data against my array containing wmcrtl window id data.
fid = focus window id
appArray contains wmctrl window id's in an array.
echo -e "-------Current array of active windows-----"
for i in ${appArray[#]}; do echo $i; done
while :
do
#UPDATES CURRENT WINDOW FOCUS
fid=$(xdotool getactivewindow)
#CHECK IF WINDOW OF FOCUS IS AT TOP OF THE ARRAY
if [ $appArray == $(printf 0x0%x $fid) ] ;
#IF IT IS THEN DO NOTHING
then
echo -e "----current window is at top of array, and active------"
else
#IF IT ISNT THEN UPDATE ARRAY
echo -e "\n------Adding new focus window to top of array------"
appArray=($(printf 0x0%x $fid) "${appArray[#]}");
#find location of duplicate if any
newArray=$(echo "${appArray[#]}" | tr ' ' '\n' | sort -u | tr '\n' ' ')
appArray=("${newArray[#]}")
#prints the array of applications
for i in ${appArray[#]}; do echo $i; done
echo "----------------------------------------------------"
fi
done

One way to do this is to shift each window in the array down one position until you find the target.
target="$(printf '0x0%x' "$fid")"
prev="$target"
# Loop over array indices
for i in "${!appArray[#]}"; do
curr="${appArray[$i]}"
appArray[$i]="$prev"
prev="$curr"
if [ "$curr" = "$target" ]; then
break
fi
done
Another way is to loop over the indices to find the index of the target, and use that to update the array.
target="$(printf '0x0%x' "$fid")"
for i in "${!appArray[#]}"; do
if [ "${appArray[$i]}" = "$target" ]; then
unset appArray[$i]
break
fi
done
appArray=("$target" "${appArray[#]}")

Related

Add each element of the array separately to the DIALOG command as a menu

I need to add each element of the array to the Dialog menu. The problem is that with the command $ {array [#]} the dialog is recognizing each word as an element. Look:
First attempt: Adding element with spaces
list=('Google Chrome' 'Brackets' 'Visual Studio') # my list
declare -a dia # dialog array
for index in ${!list[#]}; do # for each index in the list
dia[${#dia[#]}]=$index # add index
dia[${#dia[#]}]=${list[$index]} #add element
done
dialog --menu "MENU" 0 0 0 $(echo ${dia[#]})
# Format: dialog --menu "TITLE" 0 0 0 'Index1' 'Element1' 'Index2' 'Element2'...
# dia[0] = 0
# dia[1] = 'Google Chrome'
# dia[1] = 1
# dia[2] = 'Brackets'...
PRINT: first attempt
I did this to avoid processing each word, with the return of $ {list [#]} separating word by word, obtaining a sequence of number and string.
Second attempt: Replace ' ' with '-'
for index in ${!list[#]}; do
dgl[${#dgl[#]}]=$index
dgl[${#dgl[#]}]=${list[$index]/' '/'-'}
done
PRINT: Second attempt
What I think is happening
I believe that when passing the elements of the array to the DIALOG command it considers spaces ("Google Chrome", for example). Is there a way for me to show this with spaces?
Made all necessary changes to your code so it now works as expected.
#!/usr/bin/env bash
list=('Google Chrome' 'Brackets' 'Visual Studio') # my list
declare -a dia=() # dialog array
for index in "${!list[#]}"; do # for each index in the list
dia+=("$index" "${list[index]}")
done
choice=$(
dialog --menu "MENU" 0 0 0 "${dia[#]}" \
3>&1 1>&2 2>&3 3>&- # Swap stdout with stderr to capture returned dialog text
)
dialog_rc=$? # Save the dialog return code
clear # restore terminal background and clear
case $dialog_rc in
0)
printf 'Your choice was: %s\n' "${list[choice]}"
;;
1)
echo 'Cancelled menu'
;;
255)
echo 'Closed menu without choice'
;;
*)
printf 'Unknown return code from dialog: %d\n' $dialog_rc >&2
;;
esac

How do you unset all empty array elements in bash? [duplicate]

I need to remove an element from an array in bash shell.
Generally I'd simply do:
array=("${(#)array:#<element to remove>}")
Unfortunately the element I want to remove is a variable so I can't use the previous command.
Down here an example:
array+=(pluto)
array+=(pippo)
delete=(pluto)
array( ${array[#]/$delete} ) -> but clearly doesn't work because of {}
Any idea?
The following works as you would like in bash and zsh:
$ array=(pluto pippo)
$ delete=pluto
$ echo ${array[#]/$delete}
pippo
$ array=( "${array[#]/$delete}" ) #Quotes when working with strings
If need to delete more than one element:
...
$ delete=(pluto pippo)
for del in ${delete[#]}
do
array=("${array[#]/$del}") #Quotes when working with strings
done
Caveat
This technique actually removes prefixes matching $delete from the elements, not necessarily whole elements.
Update
To really remove an exact item, you need to walk through the array, comparing the target to each element, and using unset to delete an exact match.
array=(pluto pippo bob)
delete=(pippo)
for target in "${delete[#]}"; do
for i in "${!array[#]}"; do
if [[ ${array[i]} = $target ]]; then
unset 'array[i]'
fi
done
done
Note that if you do this, and one or more elements is removed, the indices will no longer be a continuous sequence of integers.
$ declare -p array
declare -a array=([0]="pluto" [2]="bob")
The simple fact is, arrays were not designed for use as mutable data structures. They are primarily used for storing lists of items in a single variable without needing to waste a character as a delimiter (e.g., to store a list of strings which can contain whitespace).
If gaps are a problem, then you need to rebuild the array to fill the gaps:
for i in "${!array[#]}"; do
new_array+=( "${array[i]}" )
done
array=("${new_array[#]}")
unset new_array
You could build up a new array without the undesired element, then assign it back to the old array. This works in bash:
array=(pluto pippo)
new_array=()
for value in "${array[#]}"
do
[[ $value != pluto ]] && new_array+=($value)
done
array=("${new_array[#]}")
unset new_array
This yields:
echo "${array[#]}"
pippo
This is the most direct way to unset a value if you know it's position.
$ array=(one two three)
$ echo ${#array[#]}
3
$ unset 'array[1]'
$ echo ${array[#]}
one three
$ echo ${#array[#]}
2
This answer is specific to the case of deleting multiple values from large arrays, where performance is important.
The most voted solutions are (1) pattern substitution on an array, or (2) iterating over the array elements. The first is fast, but can only deal with elements that have distinct prefix, the second has O(n*k), n=array size, k=elements to remove. Associative array are relative new feature, and might not have been common when the question was originally posted.
For the exact match case, with large n and k, possible to improve performance from O(nk) to O(n+klog(k)). In practice, O(n) assuming k much lower than n. Most of the speed up is based on using associative array to identify items to be removed.
Performance (n-array size, k-values to delete). Performance measure seconds of user time
N K New(seconds) Current(seconds) Speedup
1000 10 0.005 0.033 6X
10000 10 0.070 0.348 5X
10000 20 0.070 0.656 9X
10000 1 0.043 0.050 -7%
As expected, the current solution is linear to N*K, and the fast solution is practically linear to K, with much lower constant. The fast solution is slightly slower vs the current solution when k=1, due to additional setup.
The 'Fast' solution: array=list of input, delete=list of values to remove.
declare -A delk
for del in "${delete[#]}" ; do delk[$del]=1 ; done
# Tag items to remove, based on
for k in "${!array[#]}" ; do
[ "${delk[${array[$k]}]-}" ] && unset 'array[k]'
done
# Compaction
array=("${array[#]}")
Benchmarked against current solution, from the most-voted answer.
for target in "${delete[#]}"; do
for i in "${!array[#]}"; do
if [[ ${array[i]} = $target ]]; then
unset 'array[i]'
fi
done
done
array=("${array[#]}")
Here's a one-line solution with mapfile:
$ mapfile -d $'\0' -t arr < <(printf '%s\0' "${arr[#]}" | grep -Pzv "<regexp>")
Example:
$ arr=("Adam" "Bob" "Claire"$'\n'"Smith" "David" "Eve" "Fred")
$ echo "Size: ${#arr[*]} Contents: ${arr[*]}"
Size: 6 Contents: Adam Bob Claire
Smith David Eve Fred
$ mapfile -d $'\0' -t arr < <(printf '%s\0' "${arr[#]}" | grep -Pzv "^Claire\nSmith$")
$ echo "Size: ${#arr[*]} Contents: ${arr[*]}"
Size: 5 Contents: Adam Bob David Eve Fred
This method allows for great flexibility by modifying/exchanging the grep command and doesn't leave any empty strings in the array.
Partial answer only
To delete the first item in the array
unset 'array[0]'
To delete the last item in the array
unset 'array[-1]'
To expand on the above answers, the following can be used to remove multiple elements from an array, without partial matching:
ARRAY=(one two onetwo three four threefour "one six")
TO_REMOVE=(one four)
TEMP_ARRAY=()
for pkg in "${ARRAY[#]}"; do
for remove in "${TO_REMOVE[#]}"; do
KEEP=true
if [[ ${pkg} == ${remove} ]]; then
KEEP=false
break
fi
done
if ${KEEP}; then
TEMP_ARRAY+=(${pkg})
fi
done
ARRAY=("${TEMP_ARRAY[#]}")
unset TEMP_ARRAY
This will result in an array containing:
(two onetwo three threefour "one six")
Here's a (probably very bash-specific) little function involving bash variable indirection and unset; it's a general solution that does not involve text substitution or discarding empty elements and has no problems with quoting/whitespace etc.
delete_ary_elmt() {
local word=$1 # the element to search for & delete
local aryref="$2[#]" # a necessary step since '${!$2[#]}' is a syntax error
local arycopy=("${!aryref}") # create a copy of the input array
local status=1
for (( i = ${#arycopy[#]} - 1; i >= 0; i-- )); do # iterate over indices backwards
elmt=${arycopy[$i]}
[[ $elmt == $word ]] && unset "$2[$i]" && status=0 # unset matching elmts in orig. ary
done
return $status # return 0 if something was deleted; 1 if not
}
array=(a 0 0 b 0 0 0 c 0 d e 0 0 0)
delete_ary_elmt 0 array
for e in "${array[#]}"; do
echo "$e"
done
# prints "a" "b" "c" "d" in lines
Use it like delete_ary_elmt ELEMENT ARRAYNAME without any $ sigil. Switch the == $word for == $word* for prefix matches; use ${elmt,,} == ${word,,} for case-insensitive matches; etc., whatever bash [[ supports.
It works by determining the indices of the input array and iterating over them backwards (so deleting elements doesn't screw up iteration order). To get the indices you need to access the input array by name, which can be done via bash variable indirection x=1; varname=x; echo ${!varname} # prints "1".
You can't access arrays by name like aryname=a; echo "${$aryname[#]}, this gives you an error. You can't do aryname=a; echo "${!aryname[#]}", this gives you the indices of the variable aryname (although it is not an array). What DOES work is aryref="a[#]"; echo "${!aryref}", which will print the elements of the array a, preserving shell-word quoting and whitespace exactly like echo "${a[#]}". But this only works for printing the elements of an array, not for printing its length or indices (aryref="!a[#]" or aryref="#a[#]" or "${!!aryref}" or "${#!aryref}", they all fail).
So I copy the original array by its name via bash indirection and get the indices from the copy. To iterate over the indices in reverse I use a C-style for loop. I could also do it by accessing the indices via ${!arycopy[#]} and reversing them with tac, which is a cat that turns around the input line order.
A function solution without variable indirection would probably have to involve eval, which may or may not be safe to use in that situation (I can't tell).
Using unset
To remove an element at particular index, we can use unset and then do copy to another array. Only just unset is not required in this case. Because unset does not remove the element it just sets null string to the particular index in array.
declare -a arr=('aa' 'bb' 'cc' 'dd' 'ee')
unset 'arr[1]'
declare -a arr2=()
i=0
for element in "${arr[#]}"
do
arr2[$i]=$element
((++i))
done
echo "${arr[#]}"
echo "1st val is ${arr[1]}, 2nd val is ${arr[2]}"
echo "${arr2[#]}"
echo "1st val is ${arr2[1]}, 2nd val is ${arr2[2]}"
Output is
aa cc dd ee
1st val is , 2nd val is cc
aa cc dd ee
1st val is cc, 2nd val is dd
Using :<idx>
We can remove some set of elements using :<idx> also. For example if we want to remove 1st element we can use :1 as mentioned below.
declare -a arr=('aa' 'bb' 'cc' 'dd' 'ee')
arr2=("${arr[#]:1}")
echo "${arr2[#]}"
echo "1st val is ${arr2[1]}, 2nd val is ${arr2[2]}"
Output is
bb cc dd ee
1st val is cc, 2nd val is dd
http://wiki.bash-hackers.org/syntax/pe#substring_removal
${PARAMETER#PATTERN} # remove from beginning
${PARAMETER##PATTERN} # remove from the beginning, greedy match
${PARAMETER%PATTERN} # remove from the end
${PARAMETER%%PATTERN} # remove from the end, greedy match
In order to do a full remove element, you have to do an unset command with an if statement. If you don't care about removing prefixes from other variables or about supporting whitespace in the array, then you can just drop the quotes and forget about for loops.
See example below for a few different ways to clean up an array.
options=("foo" "bar" "foo" "foobar" "foo bar" "bars" "bar")
# remove bar from the start of each element
options=("${options[#]/#"bar"}")
# options=("foo" "" "foo" "foobar" "foo bar" "s" "")
# remove the complete string "foo" in a for loop
count=${#options[#]}
for ((i = 0; i < count; i++)); do
if [ "${options[i]}" = "foo" ] ; then
unset 'options[i]'
fi
done
# options=( "" "foobar" "foo bar" "s" "")
# remove empty options
# note the count variable can't be recalculated easily on a sparse array
for ((i = 0; i < count; i++)); do
# echo "Element $i: '${options[i]}'"
if [ -z "${options[i]}" ] ; then
unset 'options[i]'
fi
done
# options=("foobar" "foo bar" "s")
# list them with select
echo "Choose an option:"
PS3='Option? '
select i in "${options[#]}" Quit
do
case $i in
Quit) break ;;
*) echo "You selected \"$i\"" ;;
esac
done
Output
Choose an option:
1) foobar
2) foo bar
3) s
4) Quit
Option?
Hope that helps.
There is also this syntax, e.g. if you want to delete the 2nd element :
array=("${array[#]:0:1}" "${array[#]:2}")
which is in fact the concatenation of 2 tabs. The first from the index 0 to the index 1 (exclusive) and the 2nd from the index 2 to the end.
POSIX shell script does not have arrays.
So most probably you are using a specific dialect such as bash, korn shells or zsh.
Therefore, your question as of now cannot be answered.
Maybe this works for you:
unset array[$delete]
What I do is:
array="$(echo $array | tr ' ' '\n' | sed "/itemtodelete/d")"
BAM, that item is removed.
This is a quick-and-dirty solution that will work in simple cases but will break if (a) there are regex special characters in $delete, or (b) there are any spaces at all in any items. Starting with:
array+=(pluto)
array+=(pippo)
delete=(pluto)
Delete all entries exactly matching $delete:
array=(`echo $array | fmt -1 | grep -v "^${delete}$" | fmt -999999`)
resulting in
echo $array -> pippo, and making sure it's an array:
echo $array[1] -> pippo
fmt is a little obscure: fmt -1 wraps at the first column (to put each item on its own line. That's where the problem arises with items in spaces.) fmt -999999 unwraps it back to one line, putting back the spaces between items. There are other ways to do that, such as xargs.
Addendum: If you want to delete just the first match, use sed, as described here:
array=(`echo $array | fmt -1 | sed "0,/^${delete}$/{//d;}" | fmt -999999`)
Actually, I just noticed that the shell syntax somewhat has a behavior built-in that allows for easy reconstruction of the array when, as posed in the question, an item should be removed.
# let's set up an array of items to consume:
x=()
for (( i=0; i<10; i++ )); do
x+=("$i")
done
# here, we consume that array:
while (( ${#x[#]} )); do
i=$(( $RANDOM % ${#x[#]} ))
echo "${x[i]} / ${x[#]}"
x=("${x[#]:0:i}" "${x[#]:i+1}")
done
Notice how we constructed the array using bash's x+=() syntax?
You could actually add more than one item with that, the content of a whole other array at once.
In ZSH this is dead easy (note this uses more bash compatible syntax than necessary where possible for ease of understanding):
# I always include an edge case to make sure each element
# is not being word split.
start=(one two three 'four 4' five)
work=(${(#)start})
idx=2
val=${work[idx]}
# How to remove a single element easily.
# Also works for associative arrays (at least in zsh)
work[$idx]=()
echo "Array size went down by one: "
[[ $#work -eq $(($#start - 1)) ]] && echo "OK"
echo "Array item "$val" is now gone: "
[[ -z ${work[(r)$val]} ]] && echo OK
echo "Array contents are as expected: "
wanted=("${start[#]:0:1}" "${start[#]:2}")
[[ "${(j.:.)wanted[#]}" == "${(j.:.)work[#]}" ]] && echo "OK"
echo "-- array contents: start --"
print -l -r -- "-- $#start elements" ${(#)start}
echo "-- array contents: work --"
print -l -r -- "-- $#work elements" "${work[#]}"
Results:
Array size went down by one:
OK
Array item two is now gone:
OK
Array contents are as expected:
OK
-- array contents: start --
-- 5 elements
one
two
three
four 4
five
-- array contents: work --
-- 4 elements
one
three
four 4
five
To avoid conflicts with array index using unset - see https://stackoverflow.com/a/49626928/3223785 and https://stackoverflow.com/a/47798640/3223785 for more information - reassign the array to itself: ARRAY_VAR=(${ARRAY_VAR[#]}).
#!/bin/bash
ARRAY_VAR=(0 1 2 3 4 5 6 7 8 9)
unset ARRAY_VAR[5]
unset ARRAY_VAR[4]
ARRAY_VAR=(${ARRAY_VAR[#]})
echo ${ARRAY_VAR[#]}
A_LENGTH=${#ARRAY_VAR[*]}
for (( i=0; i<=$(( $A_LENGTH -1 )); i++ )) ; do
echo ""
echo "INDEX - $i"
echo "VALUE - ${ARRAY_VAR[$i]}"
done
exit 0
[Ref.: https://tecadmin.net/working-with-array-bash-script/ ]
How about something like:
array=(one two three)
array_t=" ${array[#]} "
delete=one
array=(${array_t// $delete / })
unset array_t
#/bin/bash
echo "# define array with six elements"
arr=(zero one two three 'four 4' five)
echo "# unset by index: 0"
unset -v 'arr[0]'
for i in ${!arr[*]}; do echo "arr[$i]=${arr[$i]}"; done
arr_delete_by_content() { # value to delete
for i in ${!arr[*]}; do
[ "${arr[$i]}" = "$1" ] && unset -v 'arr[$i]'
done
}
echo "# unset in global variable where value: three"
arr_delete_by_content three
for i in ${!arr[*]}; do echo "arr[$i]=${arr[$i]}"; done
echo "# rearrange indices"
arr=( "${arr[#]}" )
for i in ${!arr[*]}; do echo "arr[$i]=${arr[$i]}"; done
delete_value() { # value arrayelements..., returns array decl.
local e val=$1; new=(); shift
for e in "${#}"; do [ "$val" != "$e" ] && new+=("$e"); done
declare -p new|sed 's,^[^=]*=,,'
}
echo "# new array without value: two"
declare -a arr="$(delete_value two "${arr[#]}")"
for i in ${!arr[*]}; do echo "arr[$i]=${arr[$i]}"; done
delete_values() { # arraydecl values..., returns array decl. (keeps indices)
declare -a arr="$1"; local i v; shift
for v in "${#}"; do
for i in ${!arr[*]}; do
[ "$v" = "${arr[$i]}" ] && unset -v 'arr[$i]'
done
done
declare -p arr|sed 's,^[^=]*=,,'
}
echo "# new array without values: one five (keep indices)"
declare -a arr="$(delete_values "$(declare -p arr|sed 's,^[^=]*=,,')" one five)"
for i in ${!arr[*]}; do echo "arr[$i]=${arr[$i]}"; done
# new array without multiple values and rearranged indices is left to the reader

Bash append to end of array element

I have a command, when I run it, it output a table that looks like;
Id Name File OS Version Annotation
10 MICKEY [MICKEY_01_001] MICKEY/MICKEY.vmx windows8Server64Guest vmx-08
13 DONALD [DONALD_01_001] DONALD/DONALD.vmx windows7Server64Guest vmx-10
2 GOOFY [GOOFY_01_001] GOOFY/GOOFY.vmx windows9Server64Guest vmx-09
I then store the table in an array call TABLE and list the TABLE array, the code looks like this;
readarray -t TABLE <<< "$(command)"
IFS='|'
for i in "${TABLE[#]}"
do
echo $I
done
How do I append to the end of each array element? I want the table to be presented as following;
Id Name File OS Version Annotation
10 MICKEY [MICKEY_01_001] MICKEY/MICKEY.vmx windows8Server64Guest vmx-08 ON
13 DONALD [DONALD_01_001] DONALD/DONALD.vmx windows7Server64Guest vmx-10. OFF
2 GOOFY [GOOFY_01_001] GOOFY/GOOFY.vmx windows9Server64Guest vmx-09. ON
If you want to append ON or OFF in your array
readarray -t TABLE <<< "$(command)"
#IFS='|' why ?
for ((i=1;i<"${#TABLE[#]}";i++))
# start i=1 to preserve header
do
# condition to ON or OFF
[ "${a:=OFF}" = 'ON' ] &&a='OFF'||a='ON'
TABLE["$i"]="${TABLE["$i"]} $a"
done
for i in "${TABLE[#]}"
do
echo "$i"
done
What does the command "$(command)" do? Should we assume, that one line of the output = one string = one element of the array? If so, then this should work for you:
readarray -t TABLE <<< "$(command)"
IFS='|'
for i in "${TABLE[#]}"
do
if <condition_for_on_met>; then
echo "$i ON"
elif <condition_for_off_met>;then
echo "$i OFF"
else
echo "$i"
fi
done
But this is a general answer. You could improve your question by showing us what your input is and how is it processed before it is printed.

Bash menus from external file

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.

Add value to array using Bash script

I have written the following script ,
#!/bin/bash
My_File=Image.csv
Value=YES
cat "$My_File" | while IFS=, read first last
do
echo "first='$first' last='$last'"
if [ "$last" == "$Value" ];
then
echo Match_Found
echo $first
array+=("$first")
echo $first is Added
fi
done
echo (${#array[#]})
It dosenot add any value to the array , Could someone point out to the issue .
The Input is as follow ,
FA_2015-01_666,NO
FA_2015-01_777,YES
FA_2015-01_888,NO
FA_2015-01_999,YES
FA_2015-01_555,YES
Redirect the file in, don't pipe from cat or the loop is run in a subshell and everything in it is lost when it ends.
#!/bin/bash
My_File="Image.csv"
Value=YES
while IFS=, read first last
do
if [ "$last" == "$Value" ];
then
echo Match_Found
echo $first
array+=("$first")
echo $first is Added
fi
done < "$My_File"
echo "${#array[#]}"
I haven't done some bash coding in some time, but it should be like this (The way i do it):
#our table to insert an item to
table=( )
tableLength=${#table[#]}
#inserting items
table[$tableLength]="foo"
#you have to refresh this variable everytime
tableLength=${#table[#]}
table[$tableLength]="bar"
#or you can do this without refreshing the length variable
table[${#table[#]}]="foobar"
I recommend you use:
table=()
table[${#table[#]}]="item!"
using # as the length, and table[#] to display all items, so it gets the length of the array and sets an item to the length of an array or table

Resources