I am giving my first steps in bash and would like to create a associative array and iterate it. My instinct would be to do this:
declare -A FILES=(['SOURCE']='Source/Core/Core.js' ['DIST']='dist/Core.js')
for file in "${!FILES[#]}"; do
echo $file - $FILES[$file]
done
But output is:
SOURCE - [SOURCE]
DIST - [DIST]
and no path as expected. What am I missing? and btw, is the declare -A required?
Demo: https://ideone.com/iQpjmj
Thank you
Place your expansions around braces. Without it, $FILES and $file would expand separately.
${FILES[$file]}
A good practice also is to place them around double quotes to prevent word splitting and possible pathname expansion:
echo "$file - ${FILES[$file]}"
Test:
$ declare -A FILES=(['SOURCE']='Source/Core/Core.js' ['DIST']='dist/Core.js')
$ for file in "${!FILES[#]}"; do echo "$file - ${FILES[$file]}"; done
SOURCE - Source/Core/Core.js
DIST - dist/Core.js
Related
I am working with the ensemble of the mol2 filles located in the same directory.
structure36S.mol2 structure30S.mol2 structure21.mol2
structure36R.mol2 structure30R.mol2 Structure20R.mol2
structure35S.mol2 structure29R.mol2 Structure19R.mol2
structure35R.mol2 structure28R.mol2 Structure13R.mol2
structure34S.mol2 structure27R.mol2
structure34R.mol2 structure26.mol2 jacks18.mol2
structure33S.mol2 structure25.mol2 5p9.mol2
structure33R.mol2 structure24.mol2 Y6J.mol2
structure32R.mol2 structure23.mol2 06I.mol2
structure31R.mol2 structure22.mol2
From this data I need to make an associative array with the names of the filles (without extension (mol2)) as well as some value (7LMF) shared between all elements:
dataset=( [structure36S]=7LMF [structure36R]=7LMF [structure35S]=7LMF ...[06I]=7LMF [Y6J]=7LMF )
We may start from the following script:
for file in ./*.mol2; do
file_name=$(basename "$file" .mol2)
#some command to add the file into the array
done
How this script could be completed for the creating of the array?
I would recommend turning on nullglob otherwise the pattern will evaluate as a string when there is no match.
Use parameter expansion to remove the file extension.
If the leading './' is included, it will need to be stripped with another expansion.
#!/usr/bin/env bash
shopt -s nullglob
declare -A dataset
for file in *.mol2; do
dataset+=([${file%.*}]=7LMF)
done
for key in "${!dataset[#]}"; do echo "dataset[$key]: ${dataset[$key]}"; done
Try this Shellcheck-clean code:
#! /bin/bash -p
shopt -s nullglob
declare -A dataset
for file in *.mol2; do
file_name=${file%.mol2}
dataset[$file_name]=7LMF
done
# Show the contents of 'dataset'
declare -p dataset
I would like to concatenate unlimited numbers of arrays using shortest lines possible, so for this I did the code below:
#!/bin/bash
declare -a list1=("element1")
declare -a list2=("element2")
declare -a list3=("element3")
declare -a list4=("element4")
declare -a list
for i in {1..4}
do
list=( ${list[#]} ${list$i[#]} )
done
echo ${list[*]}
But the code above is not working because $i is not seen as variable and the error is: ${list$i[#]} bad substitution
You can use variable indirection:
for i in {1..4} ; do
ref="list$i[#]"
list+=("${!ref}")
done
echo "${list[#]}"
The following code outputs all 4 lists concatenated together.
eval echo \${list{1..4}[*]}
This code runs filename expansion over the result of list elements (* is replaced by filenames). Consider sacrificing 4 characters and doing \"\${list{1..4}[*]}\".
Note that eval is evil https://mywiki.wooledge.org/BashFAQ/048 and such code is confusing. I wouldn't write such code in a real script - I would definitely use a loop. Use shellcheck to check your scripts.
I have two shell scripts, fruits_original.sh and appending_fruits.sh. In the fruits_original.sh I have one array variable: fruits=('Apple' 'Mango' 'Guava').
What I want to do is I have to write a shell script appending_fruits.sh that will take an argument some new fruits name is Orange and will append that new fruit name to the fruits_original.sh fruits array variable.
After script run fruits array should be remain an array only and its value should be fruits=('Apple' 'Mango' 'Guava' 'Orange').
The file fruits_original.sh has this. Below is the appending_fruits.sh script by this my variable is changing into this fruits= ('Apple' 'Mango' 'Guava' 'Orange'). But when I am trying to do echo "${fruits[#]}" I am getting this error:
line 1: syntax error near unexpected token `('
Any luck ?
fruits= ('Apple' 'Mango' 'Guava')
echo "${fruits[#]}"
declare -a var=$(awk -F'=' '/^fruits=/ {print $2}' fruits_original.sh)
echo "${var[#]}"
var[${#var[#]}]='Orange'
joined=$(printf " '%s'" "${var[#]}")
echo ${joined:1}
echo "${joined[#]}"
sed -i "s/fruits=.*/fruits= ($( echo ${joined:1})) /" fruits_original.sh
Do not modify the script file. Instead, create another file and source the dynamic data from it. I have chosen the location of configuration to be in /tmp directory.
# fruits_original.sh
fruits=()
if [[ -e /tmp/fruits_original.rc ]]; then
. /tmp/fruits_original.rc
fi
some stuff
Then generate the config file. Use declare -p to safely output properly quoted variables.
# appending_fruits.sh
fruits=()
if [[ -e /tmp/fruits_original.rc ]]; then
. /tmp/fruits_original.rc
fi
fruits+=("new fruit")
decalre -p fruits > /tmp/fruits_original.rc
Put a uuid inside fruits_original.sh to recognize where is your snippet that you want to work with.
# fruits_original.sh
# snip 419d0df3-5f08-4511-ad5a-ad24db45aa6c
fruits=()
# snip 419d0df3-5f08-4511-ad5a-ad24db45aa6c
some stuff
Then extract the relevant parts with sed or other tool, declare "$part" it into a variable, append normally and then capture output from declare -p and replace the content between the marks again.
If not going with any of the above and this is only a very toy example to test some stuff, you could:
# read the line from another script
declare "$(sed '/fruits=/!d' fruits_original.sh)"
# append element
fruits+=(Orange)
# create source-able output
new="$(declare -p fruits)"
# remove declare -- in front
new="fruits=${new%*fruits=}"
# Replace the line with declare -p output.
sed -i "s/fruits=.*/fruits=$new/" fruits_original.sh
Notes:
var[${#var[#]}]='Orange' - just var+=(Orange). No need for ${#.
$( echo ${joined:1}) is a useless use of echo (unless you want word splitting and filename expansion).
check your scripts with https://shellcheck.net
fruits= ( is not an assignment and will run a subshell and could cause syntax error. There is no space in assignment around =.
declare -a var=$( - var is not an array (or, it's an array with one element).
Trying to figure out how to include all .txt files except one called manifest.txt.
FILES=(path/to/*.txt)
You can use extended glob patterns for this:
shopt -s extglob
files=(path/to/!(manifest).txt)
The !(pattern-list) pattern matches "anything except one of the given patterns".
Note that this exactly excludes manifest.txt and nothing else; mmanifest.txt, for example, would still go in to the array.
As a side note: a glob that matches nothing at all expands to itself (see the manual and this question). This behaviour can be changed using the nullglob (expand to empty string) and failglob (print error message) shell options.
You can build the array one file at a time, avoiding the file you do not want :
declare -a files=()
for file in /path/to/files/*
do
! [[ -e "$file" ]] || [[ "$file" = */manifest.txt ]] || files+=("$file")
done
Please note that globbing in the for statement does not cause problems with whitespace (even newlines) in filenames.
EDIT
I added a test for file existence to handle the case where the glob fails and the nullglob option is not set.
I think this is best handled with an associative array even if just one element.
Consider:
$ touch f{1..6}.txt manifest.txt
$ ls *.txt
f1.txt f3.txt f5.txt manifest.txt
f2.txt f4.txt f6.txt
You can create an associative array for the names you wish to exclude:
declare -A exclude
for f in f1.txt f5.txt manifest.txt; do
exclude[$f]=1
done
Then add files to an array that are not in the associative array:
files=()
for fn in *.txt; do
[[ ${exclude[$fn]} ]] && continue
files+=("$fn")
done
$ echo "${files[#]}"
f2.txt f3.txt f4.txt f6.txt
This approach allows any number of exclusions from the list of files.
FILES=($(ls /path/to/*.txt | grep -wv '^manifest.txt$'))
I have a file containing config information and a shell script that reads that file. I want to hand over values to a bash script.
file.txt
varNumber=1.1.1
varName=testThis
varFile=~/myDir/mySubDir/output.zip
myShellScript.sh
FILENAME="~/myDir/mySubDir/output.zip" <- this is what I expect from grep/awk
startNextScript.sh -f $FILENAME
I would like to extract the variables either as an associated array or - if easier - grep for them,
but as I'm not used to writing commands like this in bash I am asking for help!
Using associative array in bash:
#!/bin/bash
declare -A vars
while read -r line ; do
var=${line%%=*} # Remove everything after the first =.
value=${line#*=} # Remove everything before the first =.
vars[$var]=$value
done < file.txt
echo Number: ${vars[varNumber]}
echo Name: ${vars[varName]}
echo File: ${vars[varFile]}