Shell script: Sed substitution throwing unknown command: ` ' - arrays

I'm having some trouble getting around an issue related to performing a sed substitution with array's being passed into it as variables. I'm nearly certain it has something to do with the way I'm passing the variables, but I've been scouring for hours for a solution to no avail.
The arrays are initialized in the first six lines of code before attempting a sed substitution. The full script is below:
#!/bin/bash
scalarLineNums=( $(grep -nr 'MCSTEP\|NCSTEP\|ISAVE\|DCGRAX\|DCGRAY\|DCGRAZ\|DCSTEC\|DCTIME\|ICOUTF\|ICOUTI\|D1PEKS\|D1PEFR\|D1PEPF\|MBCON\|NBCON\|D1BNVX\|D1BNVY\|D1BNVZ\|I1BNVX\|I1BNVY\|I1BNVZ\|D1BNFX\|D1BNFY\|D1BNFZ\|D1BNAX\|D1BNAY\|D1BNAZ' Layer_Rough.Y3D | cut -d ":" -f 1) )
vectorLineNums=( $(sed -n -e '/D1BNVX\|D1BNVY\|D1BNVZ\|I1BNVX\|I1BNVY\|I1BNVZ\|D1BNFX\|D1BNFY\|D1BNFZ\|D1BNAX\|D1BNAY\|D1BNAZ/{n;=;p;}' Layer_Rough.Y3D | sed -n 1~2p) )
scalarValuesOriginal=( $(grep -nr 'MCSTEP\|NCSTEP\|ISAVE\|DCGRAX\|DCGRAY\|DCGRAZ\|DCSTEC\|DCTIME\|ICOUTF\|ICOUTI\|D1PEKS\|D1PEFR\|D1PEPF\|MBCON\|NBCON\|D1BNVX\|D1BNVY\|D1BNVZ\|I1BNVX\|I1BNVY\|I1BNVZ\|D1BNFX\|D1BNFY\|D1BNFZ\|D1BNAX\|D1BNAY\|D1BNAZ' Layer_Rough.Y3D | awk -F ' +' '{print $2}') )
vectorValuesOriginal=( $(sed -n -e '/D1BNVX\|D1BNVY\|D1BNVZ\|I1BNVX\|I1BNVY\|I1BNVZ\|D1BNFX\|D1BNFY\|D1BNFZ\|D1BNAX\|D1BNAY\|D1BNAZ/{n;=;p;}' Layer_Rough.Y3D | sed -n 2~2p) )
scalarValuesNew=( $(grep -nr 'MCSTEP\|NCSTEP\|ISAVE\|DCGRAX\|DCGRAY\|DCGRAZ\|DCSTEC\|DCTIME\|ICOUTF\|ICOUTI\|D1PEKS\|D1PEFR\|D1PEPF\|MBCON\|NBCON\|D1BNVX\|D1BNVY\|D1BNVZ\|I1BNVX\|I1BNVY\|I1BNVZ\|D1BNFX\|D1BNFY\|D1BNFZ\|D1BNAX\|D1BNAY\|D1BNAZ' new-variable-list.txt | awk -F ' +' '{print $2}') )
vectorValuesNew=( $(sed -n -e '/D1BNVX\|D1BNVY\|D1BNVZ\|I1BNVX\|I1BNVY\|I1BNVZ\|D1BNFX\|D1BNFY\|D1BNFZ\|D1BNAX\|D1BNAY\|D1BNAZ/{n;=;p;}' new-variable-list.txt | sed -n 2~2p) )
i=0
for linenumber in "${scalarLineNums[#]}"
do
sed -i "${linenumber}s/${scalarValuesOriginal[$i]}/${scalarValuesNew[$i]}/" Layer_Rough.Y3D
i=$((i+1))
done
The error I receive when trying to run the script is
sed: -e expression #1, char 2: unknown command: `
'
The for loop is an attempt to perform a substitution on a per line basis, i.e., sed 'line#s/oldvalue/newvalue/'. A few of the elements contain '+' and '-' characters as some of the values are stored in scientific notation, but do not contain any slashes or whitespace.

Related

Using "comm" to find matches between two arrays

I have two arrays, I am trying to find matching values using comm. Array1 contains some additional information in each element that I strip out for the comparison. However, I would like to keep that information after the comparison is complete.
For example:
Array1=("abc",123,"hello" "def",456,"world")
Array2=("abc")
declare -a Array1
declare -a Array2
I then compare the two arrays:
oldIFS=$IFS IFS=$'\n\t'
array3=($(comm -12 <(echo "${Array1[*]}" | awk -F "," {'print $1'} | sort) <(echo "${Array2[*]}" | sort)))
IFS=$oldIFS
Which finds the match of abc:
echo ${test3[0]}
abc
However what I want is remaining values from array1 that were not part of my comm statement.
abc,123,hello
EDIT: For more clarification
The arrays in this example are populated with dummy data.
My real example is pulling information from server logs which I am saving into array1. array1 contains (userIDs,hostIPs,count) that I want to cross reference against a list of userID's (array2). My goal is to find out what userIDs exsist in array1 and array2 and save those ID's with the additional information from array1 (hostIPs,count) into array3
array1 is populated from a variable that is is the results of a curl command that generates a splunk search. The data returned looks like this:
"uniqueID=<ID>","<IP>","<hostname>",1
I save the results of the splunk report as $splunk, and then decalare array1 with the results of $splunk - the header information since the results come back in csv format
array1=( $(echo $splunk | sed 's/ /\n/g' | sed 1d) )
array2 is generated from a master file that I have stored locally. That contains all the application ID's in our ecosystem. For example
uid=<ID>
I cat the contents of the master file into array2
array2=( $(cat master.txt) )
I then want to find what IDs from array1 exsist in array2 and save that as array3. This requires some massaging of the data in array1 to make it match the format of array2.
oldIFS=$IFS IFS=$'\n\t'
array3=($(comm -12 <(echo "${array1[*]}" | sed 's/ /\n/g' | awk -F "\"," {'print $1'} | sed 's/\"//g' | sed 's/|/ /g' | awk -F$'=' -v OFS=$'=' '{ $1 = "uid" }1' | grep -i "OU=People" | sed 's/OU/ou/g' | sort) <(echo "${array2[*]}" | sort)))
IFS=$oldIFS
array 3 will then contain lines that match in both arrays
uid=<ID>
uid=<ID>
However I am looking for something more along the line of
"uid=<ID>","<IP>","<hostname>",1
"uid=<ID>","<IP>","<hostname>",1
I would do it like this:
join -t, \
<(printf '%s\n' "${Array1[#]}" | sort -t, -k1,1) \
<(printf '%s\n' "${Array2[#]}" | sort)
Use the join command with , as the field delimiter. The first "file" is the first array, one element per line, sorted on the first field (comma delimited); the second "file" is the second array, one element per line, sorted.
The output will be every line where the first element of the first file matches the element from the second file; for the example input it's
abc,123,hello
This makes only one assumption, namely that no array element contains a newline. To make it more robust (assuming GNU Coreutils), we can use NUL as the delimiter:
join -z -t, \
<(printf '%s\0' "${Array1[#]}" | sort -z -t, -k1,1) \
<(printf '%s\0' "${Array2[#]}" | sort -z)
This prints the output separated by NUL as well; to read the result into an array, we can use readarray:
readarray -d '' -t Array3 < <(
join -z -t, \
<(printf '%s\0' "${Array1[#]}" | sort -z -t, -k1,1) \
<(printf '%s\0' "${Array2[#]}" | sort -z)
)
readarray -d requires Bash 4.4 or newer. For older Bash, you can use a loop:
while IFS= read -r -d '' element; do
Array3+=("$element")
done < <(
join -z -t, \
<(printf '%s\0' "${Array1[#]}" | sort -z -t, -k1,1) \
<(printf '%s\0' "${Array2[#]}" | sort -z)
)
I don't know how to do this with comm, but I do have a solution for you with sed and grep. The following commands match on the regex uid=X,, where the string/array is in the form of uid=x or (uid=x uid=y) respectively.
# Array 2 (B) is a string
$ A=("uid=1,10.10.10.1,server1,1" "uid=2,10.10.10.2,server2,1")
$ B="uid=1"
$ echo ${A[#]} | grep -oE "([^ ]*${B},[^ ]*)"
uid=1,10.10.10.1,server1,1
# Array 2 (D) is an array
$ C=(${A[#]} "uid=3,10.10.10.3,server3,1" "uid=4,10.10.10.4,server4,1")
$ D=(${B} "uid=3")
$ echo ${C[*]} | grep -oE "([^ ]*($(echo ${D[#]} | sed 's/ /,|/g'))[^ ]*)"
uid=1,10.10.10.1,server1,1
uid=3,10.10.10.3,server3,1
# Content of arrays
$ echo ${A[#]}
uid=1,10.10.10.1,server1,1 uid=2,10.10.10.2,server2,1
$ echo ${B}
uid=1
$ echo ${C[#]}
uid=1,10.10.10.1,server1,1 uid=2,10.10.10.2,server2,1 uid=3,10.10.10.3,server3,1 uid=4,10.10.10.4,server4,1
$ echo ${D[#]}
uid=1 uid=3

Read delimited multiline string file into multiple arrays in Bash

I began with a file like so:
Table_name1 - Table_desc1
Table_name2 - Table_desc2
...
...
I have a script that parses this file and splits them into two arrays:
declare -a TABLE_IDS=()
declare -a TABLE_DESCS=()
while IFS= read -r line || [[ -n "${line}" ]]; do
TABLE_IDS[i]=${line%' '-' '*}
TABLE_DESCS[i++]=${line#*' '-' '}
done < "${TABLE_LIST}"
for i in "${!TABLE_IDS[#]}"; do
echo "Creating Table ID: "${TABLE_IDS[i]}", with Table Description: "${TABLE_DESCS[i]}""
done
This works really well, with no problems whatsoever.
I wanted to extend this and make the file:
Table_name1 - Table_desc1 - Table_schema1
Table_name2 - Table_desc2 - Table_schema2
...
...
For this, I tried:
declare -a TABLE_IDS=()
declare -a TABLE_DESCS=()
while IFS= read -r line || [[ -n "${line}" ]]; do
TABLE_IDS[i]="$(echo $line | cut -f1 -d - | tr -d ' ')"
TABLE_DESCS[i++]="$(echo $line | cut -f2 -d - | tr -d ' ')"
TABLE_SCHEMAS[i++]="$(echo $line | cut -f3 -d - | tr -d ' ')"
done < "${TABLE_LIST}"
for i in "${!TABLE_IDS[#]}"; do
echo "Creating Table ID: "${TABLE_IDS[i]}", with Table Description: "${TABLE_DESCS[i]}" and schema: "${TABLE_SCHEMAS[i]}""
done
And while this will faithfully list all the Table IDs and the Table descriptions, the schemas are omitted. I tried:
while IFS= read -r line || [[ -n "${line}" ]]; do
TABLE_IDS[i]="$(echo $line | cut -f1 -d - | tr -d ' ')"
TABLE_DESCS[i]="$(echo $line | cut -f2 -d - | tr -d ' ')"
TABLE_SCHEMAS[i]="$(echo $line | cut -f3 -d - | tr -d ' ')"
done < "${TABLE_LIST}"
And it returns just the last line's Table name, description AND schema. I suspect this is an indexing/looping problem, but am unable to figure out what exactly is going wrong. Please help! Thanks!
perhaps set the delimiter to the actual delimiter - and do the processing in the read loop instead of deferring and using arrays.
$ while IFS=- read -r t d s;
do
echo "Creating Table ID: ${t// }, with Table Description: ${d// } and schema: ${s// }";
done < file

create arrays from for loop output

I'm trying to understand what I'm doing wrong here, but can't seem to determine the cause. I would like to create a set of arrays from an output for a for loop in bash. Below is the code I have so far:
for i in `onedatastore list | grep pure02 | awk '{print $1}'`;
do
arr${i}=($(onedatastore show ${i} | sed 's/[A-Z]://' | cut -f2 -d\:)) ;
echo "Output of arr${i}: ${arr${i}[#]}" ;
done
The output for the condition is as such:
107
108
109
What I want to do is based on these unique IDs is create arrays:
arr107
arr108
arr109
The arrays will have data like such in each:
[oneadmin#opennebula/]$ arr107=($(onedatastore show 107 | sed 's/[A-Z]://' | cut -f2 -d\:))
[oneadmin#opennebula/]$ echo ${arr107[#]}
DATASTORE 107 INFORMATION 107 pure02_vm_datastore_1 oneadmin oneadmin 0 IMAGE vcenter vcenter /var/lib/one//datastores/107 FILE READY DATASTORE CAPACITY 60T 21.9T 38.1T - PERMISSIONS um- u-- --- DATASTORE TEMPLATE CLONE_TARGET="NONE" DISK_TYPE="FILE" DS_MAD="vcenter" LN_TARGET="NONE" RESTRICTED_DIRS="/" SAFE_DIRS="/var/tmp" TM_MAD="vcenter" VCENTER_CLUSTER="CLUSTER01" IMAGES
When I try this in the script section though I get output errors as such:
./test.sh: line 6: syntax error near unexpected token `$(onedatastore show ${i} | sed 's/[A-Z]://' | cut -f2 -d\:)'
I can't seem to figure out the syntax to use on this scenario.
In the end what I want to do is be able to compare different datastores and based on which on has more free space, deploy VMs to it.
Hope someone can help. Thanks
You can use the eval (potentially unsafe) and declare (safer) commands:
for i in $(onedatastore list | grep pure02 | awk '{print $1}');
do
declare "arr$i=($(onedatastore show ${i} | sed 's/[A-Z]://' | cut -f2 -d\:))"
eval echo 'Output of arr$i: ${arr'"$i"'[#]}'
done
readarray or mapfile, added in bash 4.0, will read directly into an array:
while IFS= read -r i <&3; do
readarray -t "arr$i" < <(onedatastore show "$i" | sed 's/[A-Z]://' | cut -f2 -d:)
done 3< <(onedatastore list | awk '/pure02/ {print $1}')
Better, back through bash 3.x, one can use read -a to read to an array:
shopt -s pipefail # cause pipelines to fail if any element does
while IFS= read -r i <&3; do
IFS=$'\n' read -r -d '' -a "arr$i" \
< <(onedatastore show "$i" | sed 's/[A-Z]://' | cut -f2 -d: && printf '\0')
done 3< <(onedatastore list | awk '/pure02/ {print $1}')
Alternately, one can use namevars to create an alias for an array with an arbitrarily-named array in bash 4.3:
while IFS= read -r i <&3; do
declare -a "arr$i"
declare -n arr="arr$i"
# this is buggy: expands globs, string-splits on all characters in IFS, etc
# ...but, well, it's what the OP is asking for...
arr=( $(onedatastore show "$i" | sed 's/[A-Z]://' | cut -f2 -d:) )
done 3< <(onedatastore list | awk '/pure02/ {print $1}')

How to deny empty array indexes in bash

I wrote the bash like so:
#!/bin/bash
GAP=1
Out=$1
ResultFile=$2
len=`wc -l $Out | awk '{print $1}'`
eval "(COMMAND) &"
pid=$!
i=0
while kill -0 $pid; do
if [ -N $Out ]; then
newlen=`wc -l $Out | awk '{print $1}'`
newlines=`expr $newlen - $len`
tail -$newlines $Out > temp
IP=( $(sed -n '<SomeThing>' temp) )
host=${IP[$i]}
echo "exit" | nc $host 23
if [ "$?" -eq "0" ]; then
(
<DoingSomeThing>
) | nc $host 23 1>>$ResultFile 2>&1
fi
len=$newlen
let i++
fi
sleep $GAP
done
When the command IP=( $(sed -n '<SomeThing>' temp) ) is running in my bash maybe the result of sed command is nothing and maybe the output is ip. I want only when output of sed command get ip write it into array and when the output of sed is empty does not write it to array.
Thank you
You're not doing your script right in many ways but about your question, the quick way is to store the output first on a variable:
SED_OUT=$(sed -n '<SomeThing>' temp)
[[ -n $SED_OUT ]] && IP=($SED_OUT) ## Would only alter IP if $SED_OUT has a value.

Adding a string to the front of array loop?

#!/bin/bash
#OLDIFS=$IFS
IFS=$'\r'
fortune_lines=($(fortune | fold -w 90))
#Screen_Session=$"{mainscreen}"
Screen_Session=`screen -ls|grep "\."|grep "("|awk '{print $1}'`
Screen_OneLiner=$(screen -p 0 -S ${Screen_Session} -X stuff "`printf "${fortune_lines[#]}\r"`")
#IFS=$OLDIFS;
for var in "${Screen_OneLiner[#]}"
do
echo "${var}"
done
ok this script works (sorta). I need to at the string "say " to front of the entire array index. Currently I can only get it to print out "say " to the first line.
We need not complicate things with contraptions like arrays - we can let good old sed do the job.
#!/bin/bash
fortune_lines=$(fortune | fold -w 90 | sed 's/^/say /')
Screen_Session=`screen -ls|grep "\."|grep "("|awk '{print $1}'`
screen -p 0 -S $Screen_Session -X stuff "$fortune_lines"

Resources