Accessing Perl array in bash [duplicate] - linux

This question already has answers here:
Set a shell array from an array in Perl script
(3 answers)
Closed 6 years ago.
I have a perl code where I execute some bash commands using backticks. I want to read a perl array in that bash command. My array has some strings and I want to read them in a for loop of bash.
my #aArray = (1,2,3,4);
my $command = 'for i in $#aArray; do xxxxx $i; done;';
`$command`
I also want to catch error if any part of the loop fails. Thanks

As #chepner has suggested, the code you want looks a bit like this:
my #array = (1, 2, 3, 4);
for my $val (#array) {
# Pick your favourite/the most appropriate mechanism for making system calls
system("command", $val);
}
If you need to make a single call on a remote system, what you could do is something like this:
my #array = (1, 2, 3, 4);
my $command = "for i in ("
for my $val(#array) {
$val =~ s/(?<!\\) /\\ /g; # Escape spaces that haven't already been (if the array elements might contain them)
$command = "$command $val";
}
$command = $command."); do <command> $i; done;";
system($command);

Related

creating multiple user named and numbered files without loop from bash

Totally new in bash programming and got a problem like this:
N empty files should be created. The file sum N and also the file name should be given from users via command line.
no loop and getopts should be used.
I've tried something like this which i found from google but it doesn't works. I got confused between linux and windows bash scripts. Hope someone can help me with the problem. Thank you!
#!/bin/bash
echo $N
:start
if [ $N > 0 ]
then
touch xyzfile_$N
$N = $N - 1
pause
goto start
else
then
echo "no data will be created"
fi
The command line and the result should be (for example if N=7) look like this:
command line:
./createfiles -n filename 7
expected result:
filename_1,filename_2,filename_3...filename_7
You can do this using a recursive shell function:
fn() {
# add some sanity checks to check parameters
touch "$1_${2}"
(($2 > 1)) && fn "$1" $(($2 - 1))
}
This part is a recursive call:
(($2 > 1)) && fn "$1" $(($2 - 1))
Where it basically calls itself by decrementing 1 from 2nd argument as long as $2 is greater than 1.
Then just call it as:
fn filename 7
It will create 7 files as:
filename_1 filename_2 filename_3 filename_4 filename_5 filename_6 filename_7
I ended up the question with a for-loop like this:
createfile() {
name="$1"
num="$2"
for((i=1;i<=num;i++))
do
touch "$1_${i}"
done
}
createfile $1 ${2}
It works great actually. And now I tried to solve the problem with getopts and it seems a function call doesn't work in the case option

How to extract key value pairs from a file when values span multiple lines?

I'm a few weeks into bash scripting and I haven't advanced enough yet to get my head wrapped around this problem. Any help would be appreciated!
I have a "script.conf" file that contains the following:
key1=value1
key2=${HOME}/Folder
key3=( "k3v1" "k3 v2" "k3v3")
key4=( "k4v1"
"k4 v2"
"k4v3"
)
key5=value5
#key6="Do Not Include Me"
In a bash script, I want to read the contents of this script.conf file into an array. I've learned how to handle the scenarios for keys 1, 2, 3, and 5, but the key4 scenario throws a wrench into it with it spanning across multiple lines.
I've been exploring the use of sed -n '/=\s*[(]/,/[)]/{/' which does capture key4 and its value, but I can't figure out how to mix this so that the other keys are also captured in the matches. The range syntax is also new to me, so I haven't figured out how to separate the key/value. I feel like there is an easy regex that would accomplish what I want... in plain-text: "find and group the pattern ^(.*)= (for the key), then group everything after the '=' char until another ^(.*)= match is found, rinse and repeat". I guess if I do this, I need to change the while read line to not handle the key/value separation for me (I'll be looking into this while I'm waiting for a response). BTW, I think a solution where the value of key4 is flattened (new lines removed) would be acceptable; I know for key3 I have to store the value as a string and then convert it to an array later when I want to iterate over it since an array element apparently can't contain a list.
Am I on the right path with sed or is this a job for awk or some other tool? (I haven't ventured into awk yet). Is there an easier approach that I'm missing because I'm too deep into the forest (like changing the while read line in the LoadConfigFile function)?
Here is the code that I have so far in script.sh for processing and capturing the other pairs into the $config array:
__AppDir=$(dirname $0)
__AppName=${__ScriptName%.*}
typeset -A config #init config array
config=( #Setting Default Config values
[key1]="defaultValue1"
[key2]="${HOME}/defaultFolder"
[QuietMode]=0
[Verbose]=0 #Ex. Usage: [[ "${config[Verbose]}" -gt 0 ]] && echo ">>>Debug print"
)
function LoadConfigFile() {
local cfgFile="${1}"
shopt -s extglob #Needed to remove trailing spaces
if [ -f ${cfgFile} ]; then
while IFS='=' read -r key value; do
if [[ "${key:0:1}" == "#" ]]; then
#echo "Skipping Comment line: ${key}"
elif [ "${key:-EMPTY}" != "EMPTY" ]; then
value="${value%%\#*}" # Delete in-line, right comments
value="${value%%*( )}" # Delete trailing spaces
value="${value%%( )*}" # Delete leading spaces
#value="${value%\"*}" # Delete opening string quotes
#value="${value#\"*}" # Delete closing string quotes
#Manipulate any variables included in the value so that they can be expanded correctly
# - value must be stored in the format: "${var1}". `backticks`, "$var2", and "doubleQuotes" are left as is
value="${value//\"/\\\"}" # Escape double quotes for eval
value="${value//\`/\\\`}" # Escape backticks for eval
value="${value//\$/\\\$}" # Escape ALL '$' for eval
value="${value//\\\${/\${}" # Undo the protection of '$' if it was followed by a '{'
value=$(eval "printf '%s\n' \"${value}\"")
config[${key}]=${value} #Store the value into the config array at the specified key
echo " >>>DBG: Key = ${key}, Value = ${value}"
#else
# echo "Skipped Empty Key"
fi
done < "${cfgFile}"
fi
}
CONFIG_FILE=${__AppDir}/${__AppName}.conf
echo "Config File # ${CONFIG_FILE}"
LoadConfigFile ${CONFIG_FILE}
#Print elements of $config
echo "Script Config Values:"
echo "----------------------------"
for key in "${!config[#]}"; do #The '!' char gets an array of the keys, without it, we would get an array of the values
printf " %-20s = %s\n" "${key}" "${config[${key}]}"
done
echo "------ End Script Config ------"
#To convert to an array...
declare -a valAsArray=${config[RequiredAppPackages]} #Convert the value from a string to an array
echo "Count = ${#valAsArray[#]}"
for itemCfg in "${valAsArray[#]}"; do
echo " item = ${itemCfg}"
done
As I mentioned before, I'm just starting to learn bash and Linux scripting in general, so if you see that I'm doing some taboo things in other areas of my code too, please feel free to provide feedback in the comments... I don't want to start bad habits early on :-).
*If it matters, the OS is Ubuntu 14.04.
EDIT:
As requested, after reading the script.conf file, I would like for the elements in $config[#] to be equivalent to the following:
typeset -A config #init config array
config=(
[key1]="value1"
[key2]="${HOME}/Folder"
[key3]="( \"k3v1\" \"k3 v2\" \"k3v3\" )"
[key4]="( \"k4v1\" \"k4 v2\" \"k4v3\" )"
[key5]="value5"
)
I want to be able to convert the values of elements 'key4' and 'key3' into an array and iterated over them the same way in the following code:
declare -a keyValAsArray=${config[keyN]} #Convert the value from a string to an array
echo "Count = ${#keyValAsArray[#]}"
for item in "${keyValAsArray[#]}"; do
echo " item = ${item}"
done
I don't think it matters if \n is preserved for key4's value or not... that depends on if declare has a problem with it.
A shell is an environment from which to call tools with a language to sequence those calls. It is NOT a tool to manipulate text. The standard UNIX tool to manipulate text is awk. Trying to manipulate text in shell IS a bad habit, see why-is-using-a-shell-loop-to-process-text-considered-bad-pr‌​actice for SOME of the reasons why
You still didn't post the expected result of populating the config array so I'm not sure but I think this is what you wanted:
$ cat tst.sh
declare -A config="( $(awk '
{ gsub(/^[[:space:]]+|([[:space:]]+|#.*)$/,"") }
!NF { next }
/^[^="]+=/ {
name = gensub(/=.*/,"",1)
value = gensub(/^[^=]+=/,"",1)
n2v[name] = value
next
}
{ n2v[name] = n2v[name] OFS $0 }
END {
for (name in n2v) {
value = gensub(/"/,"\\\\&","g",n2v[name])
printf "[%s]=\"%s\"\n", name, value
}
}
' script.conf
) )"
declare -p config
$ ./tst.sh
declare -A config='([key5]="value5" [key4]="( \"k4v1\" \"k4 v2\" \"k4v3\" )" [key3]="( \"k3v1\" \"k3 v2\" \"k3v3\")" [key2]="/home/Ed/Folder" [key1]="value1" )'
The above uses GNU awk for gensub(), with other awks you'd use [g]sub() instead.

Bash declaring variable with a number inside a for loop [duplicate]

This question already has answers here:
Dynamic variable names in Bash
(19 answers)
Closed 7 years ago.
I can't find an answer to this problem, other than people asking to use an array instead. That's not what I want. I want to declare a number of variables inside a for loop with the same name except for an index number.
I=0
For int in $ints;
Do i=[$i +1]; INTF$i=$int; done
It doesn't really work. When I run the script it thinks the middle part INTF$i=$int is a command.
Without an array, you need to use the declare command:
i=0
for int in $ints; do
i=$((i +1))
declare "intf$i=$int"
done
With an array:
intf=()
for int in $ints; do
intf+=( $int )
done
Bash doesn't handle dynamic variable names nicely, but you can use an array to keep variables and results.
[/tmp]$ cat thi.sh
#!/bin/bash
ints=(data0 data1 data2)
i=0
INTF=()
for int in $ints
do
((i++))
INTF[$i]=$int
echo "set $INTF$i INTF[$i] to $int"
done
[/tmp]$ bash thi.sh
set 1 INTF[1] to data0

Bash for loop parameter unexpected behaviour [duplicate]

This question already has answers here:
Variables in bash seq replacement ({1..10}) [duplicate]
(7 answers)
Brace expansion with a Bash variable - {0..$foo}
(5 answers)
Closed 8 years ago.
I'm making a program in bash that creates a histoplot, using numbers I have created. The numbers are stored as such (where the 1st number is how many words are on a line of a file, and the 2nd number is how many times this amount of words on a line comes up, in a given file.)
1 1
2 4
3 1
4 2
this should produce:
1 #
2 ####
3 #
4 ##
BUT the output I'm getting is:
1 #
2 #
3 #
4 #
however the for loop is not recognising that my variable "hashNo" is a number.
#!/bin/bash
if [ -e $f ] ; then
while read line
do
lineAmnt=${line% *}
hashNo=${line##* }
#VVVV Problem is this line here VVVV
for i in {1..$hashNo}
#This line ^^^^^^^ the {1..$hashNo}
do
hashes+="#"
done
printf "%4s" $lineAmnt
printf " $hashes\n"
hashes=""
done < $1
fi
the code works if I replace hashNo with a number (eg 4 makes 4 hashes in my output) but it needs to be able to change with each line (no all lines on a file will have the same amount of chars in them.
thanks for any help :D
A sequence expression in bash must be formed from either integers or characters, no parameter substitutions take place before hand. That's because, as per the bash doco:
The order of expansions is: brace expansion, tilde expansion, parameter, variable and arithmetic expansion and command substitution (done in a left-to-right fashion), word splitting, and pathname expansion.
In other words, brace expansion (which includes the sequence expression form) happens first.
In any case, this cries out to be done as a function so that it can be done easily from anywhere, and also made more efficient:
#!/bin/bash
hashes() {
sz=$1
while [[ $sz -ge 10 ]]; do
printf "##########"
((sz -= 10))
done
while [[ $sz -gt 0 ]]; do
printf "#"
((sz--))
done
}
echo 1 "$(hashes 1)"
echo 2 "$(hashes 4)"
echo 3 "$(hashes 1)"
echo 4 "$(hashes 2)"
which outputs, as desired:
1 #
2 ####
3 #
4 ##
The use of the first loop (doing ten hashes at a time) will almost certainly be more efficient than adding one character at a time and you can, if you wish, do a size-50 loop before that for even more efficiencies if your values can be larger.
I tried this for (( i=1; i<=$hashNo; i++ )) for the for loop, it seems to be working
Your loop should be
for ((i=0; i<hashNo; i++))
do
hashes+="#"
done
Also you can stick with your loop by the use of eval and command substitution $()
for i in $(eval echo {1..$hashNo})
do
hashes+="#"
done

Shell Programming: Access Element of List

It is my understanding that when writing a Unix shell program you can iterate through a string like a list with a for loop. Does this mean you can access elements of the string by their index as well?
For example:
foo="fruit vegetable bread"
How could I access the first word of this sentence? I've tried using brackets like the C-based languages to no avail, and solutions I've read online require regular expressions, which I would like to avoid for now.
Pass $foo as argument to a function. Than you can use $1, $2 and so on to access the corresponding word in the function.
function try {
echo $1
}
a="one two three"
try $a
EDIT: another better version is:
a="one two three"
b=( $a )
echo ${b[0]}
EDIT(2): have a look at this thread.
Using arrays is the best solution.
Here's a tricky way using indirect variables
get() { local idx=${!#}; echo "${!idx}"; }
foo="one two three"
get $foo 1 # one
get $foo 2 # two
get $foo 3 # three
Notes:
$# is the number of parameters given to the function (4 in all these cases)
${!#} is the value of the last parameter
${!idx} is the value of the idx'th parameter
You must not quote $foo so the shell can split the string into words.
With a bit of error checking:
get() {
local idx=${!#}
if (( $idx < 1 || $idx >= $# )); then
echo "index out of bounds" >&2
return 1
fi
echo "${!idx}"
}
Please don't actually use this function. Use an array.

Resources