Remove mutiline string after equal sign - bash

I am trying to get IAM policies from AWS and add it to a text file in the below specific format. I want to delete everything after policy= in the file before end of the "}" bracket.
This is the text file sample I have. But the original file can have multiple instances of example_policy in the same file.
"example1_policy" {
name="example"
policy=jsonencode(
{
Statement=[
{
Action=[
s3:*
]
Effect="Allow"
},
]
Version="2012-10-17"
}
)
}
"example2_policy" {
name="example2"
policy=jsonencode(
{
Statement=[
{
Action=[
s3:*
]
Effect="Allow"
},
]
Version="2012-10-17"
}
)
}
"example3_policy" {
name="example3"
policy=jsonencode(
{
Statement=[
{
Action=[
s3:*
]
Effect="Allow"
},
]
Version="2012-10-17"
}
)
}
Expected Output:
"example1_policy" {
name="example1"
policy=
}
"example2_policy" {
name="example2"
policy=
}
"example3_policy" {
name="example3"
policy=
}
or
"example1_policy" {
name="example1"
policy=<placeholder>
}
"example2_policy" {
name="example2"
policy=<placeholder>
}
"example3_policy" {
name="example3"
policy=<placeholder>
}
As per #Wiktor's comment I tried out this command
sed -i '/policy=/,/^\s*)\s*$/d' test.txt
Output: policy= should remain intact.
"example_policy" {
name="example"
}

You can use the following GNU sed command:
sed -i '/policy=/,/^\s*)\s*$/{/policy=/!d};s/\(policy=\).*/\1<placehlder>/' file
See the online demo. Details:
/policy=/,/^\s*)\s*$/ - finds blocks of lines between a line with policy= and a line that contains only a ) char enclosed with zero or more whitespaces
{/policy=/!d} - prevents the first line in the found block to be removed and removed the other line(s)
s/\(policy=\).*/\1<placehlder>/ - replaces all after policy= with <placeholder>.
If there is a need to match policy=, then any chars up to ( and then up to the next balanced ) char, you can use perl command like
perl -0777 -i -pe 's/^(\h*policy=)[^()]*(\((?:[^()]++|(?2))*\))/$1<placehlder>/gm' file
Details:
-0777 - read the file into a single string so that line breaks and the lines
-i - inline file change
^ - start of a line (due to m flag)
(\h*policy=) - Group 1 ($1): zero or more horizontal whitespaces
[^()]* - zero or more chars other than ( and )
(\((?:[^()]++|(?2))*\)) - Group 2 (for recursion): \( - a ( char, (?:[^()]++|(?2))* - zero or more sequences of one or more chars other than ( and ) or the whole Group 2 pattern recursed, and \) matches a ) char
$1<placehlder> - the replacement is Group 1 + <placeholder> string
See the online demo and the regex demo:
#!/bin/bash
s='"example1_policy" {
name="example"
policy=jsonencode(
{
Statement=[
{
Action=[
s3:*
]
Effect="Allow"
},
]
Version="2012-10-17"
}
)
}
"example2_policy" {
name="example2"
policy=jsonencode(
{
Statement=[
{
Action=[
s3:*
]
Effect="Allow"
},
]
Version="2012-10-17"
}
)
}
"example3_policy" {
name="example3"
policy=jsonencode(
{
Statement=[
{
Action=[
s3:*
]
Effect="Allow"
},
]
Version="2012-10-17"
}
)
}'
perl -0777 -pe 's/^(\h*policy=)[^()]*(\((?:[^()]++|(?2))*\))/$1<placehlder>/gm' <<< "$s"
Output:
"example1_policy" {
name="example"
policy=<placehlder>
}
"example2_policy" {
name="example2"
policy=<placehlder>
}
"example3_policy" {
name="example3"
policy=<placehlder>
}

You could do this quite easily in Python since you have state:
def clean(s, pattern='policy='):
pre, post = s.split(pattern)
harmony = True
quote = False
braces = 0
for i, char in enumerate(post):
if harmony and char == '\n':
return f'{pre}{pattern}{post[i:]}'
if not quote:
if char == '(':
braces += 1
elif char == ')':
braces -= 1
elif char in ('"', "'"):
quote = char
harmony = braces == 0
elif quote == char:
quote = False
This would even ignore braces that are enclosed in strings (both " and ' strings).
So, this version works on trickier strings too:
"example_policy" {
name="example"
policy=jsonencode(
{
Statement=[
{
Action=[
s3:*
]
Effect="Al)l'ow"
},
]
Version='"2012-10)-17"'
}
)
}
You can easily extend this to support other types of braces or quotation. The only difference is that you need to use counters for braces since the opening and the closing characters are different, while for quotations you just need to add extra characters to the matching list - since everything else within the quotation is ignored, you just need to remember which character the quote was opened with.
Doing this with regexes would be tricker since they only support finite brace nesting.
To remove multiple policies within the same string we need to define a helper function:
def clean(s):
harmony = True
quote = False
braces = 0
for i, char in enumerate(s):
if harmony and char == '\n':
return s[i:]
if not quote:
if char == '(':
braces += 1
elif char == ')':
braces -= 1
elif char in ('"', "'"):
quote = char
harmony = braces == 0
elif quote == char:
quote = False
return s
And then the main function that will apply the "cleaning helper" to each individual chunk:
def clean_all(s, pattern='policy='):
head, *tail = s.split(pattern)
return f'{head}{pattern}{pattern.join(clean(part) for part in tail)}'

Related

set a value with jq names with full stops in them

I'm wondering how i can escape the newValue so jq replaces the value in :
#!/bin/sh
test="{ \"one.one\" : { \"version\" : \"0.0.1\" }, \"two.two\" : { \"version\" : \"0.0.2\" } }"
newvalue="3.3.3"
newjson=$(echo $test | jq '."two.two".version = "$newvalue" ')
echo $newjson
outputs:
{ "one.one": { "version": "0.0.1" }, "two.two": { "version": "$newvalue" } }
none of the escapes i tried \" \' around $newvalue seems to work
First of all, to use a variable, it can't be in quotes. Replace
"$newvalue"
with
$newvalue
Secondly, you never set $newvalue in the jq program. To achieve that, you can use the following:
--arg newvalue "$newvalue"

How to print a value in single line using shell command?

I have written a shell script in a groovy function which should return the the output (in a single line) as abcd-def-chart but, I am getting the output as shown below in 2 different lines:
abcd-def
-chart
My groovy code:
String getChartName(Map configuration = [:]) {
if (configuration.chartName != null) {
return configuration.chartName
}
chartName = ""
if (configuration.buildSystem == 'maven') {
chartName = getMavenProjectName() + "-chart"
}
echo "chartName: ${chartName}"
return chartName
}
String getMavenProjectName() {
echo "inside getMavenProjectName +++++++"
def mavenChartName = sh returnStdout:true, script: '''
#!/bin/bash
GIT_LOG=$(env -i git config --get remote.origin.url)
basename "$GIT_LOG" .git; '''
echo "mavenChartName: ${mavenChartName}"
return mavenChartName
}

Looping over a bash array to add new data with jq -- only seeing the last change

I have a Bash script that uses jq and a for loop to iterate through an array, grab a directory that I need to be monitored by Amazon CloudWatch, and stick it into the latter's JSON configuration file. However, for some reason, only the last item in the array is actually being written. I assume there's something in my logic that is not appending my changes, and instead overwriting them in a particular place, but I can't quite figure out the fix.
Here is my code:
logPaths=("/shared/logs/application/application1"
"/shared/logs/application/application2"
"/shared/logs/application/application3")
# Loop through array to create stanzas and export them to the temp file
for i in ${logPaths[#]}; do
jq "
.logs.logs_collected.files.collect_list[-1] |= . + {
\"file_path\": \"$i\",
\"log_group_name\": \"/aws-account/aws/ec2/syslogs\",
\"log_stream_name\": \"$definedElsewhere\",
\"timestamp_format\": \"%b %d %H:%M:%S\"}" \
/opt/aws/amazon-cloudwatch-agent/amazon-cloudwatch-agent.json \
> /opt/aws/amazon-cloudwatch-agent/amazon-cloudwatch-agent.json.tmp \
&& cp /opt/aws/amazon-cloudwatch-agent/amazon-cloudwatch-agent.json.tmp /opt/aws/amazon-cloudwatch-agent/amazon-cloudwatch-agent.json
done
When this is executed, and I look at amazon-cloudwatch-agent.json, only a record for the 3rd entry in the array (/application3) appears in the configuration file.
I can't reproduce your bug -- but it's irrelevant, because if this were correctly written there wouldn't be any loop needed at all.
Using jq --args allows the logPaths array to be passed in as a set of positional arguments, and referred to from within the relevant jq code as $ARGS.positional. Thus:
#!/usr/bin/env bash
logPaths=("/shared/logs/application/application1"
"/shared/logs/application/application2"
"/shared/logs/application/application3")
# Make up some sample input, since the OP didn't provide any
cat >old.json <<'EOF'
{
"logs": {
"logs_collected": {
"files": {
"collect_list": [
{"test": "make sure this old data is retained"}
]
}
}
}
}
EOF
jq --arg definedElsewhere "Other Value" '
($ARGS.positional | [
.[] | { "file_path": .,
"log_group_name": "/aws-account/aws/ec2/syslogs",
"log_stream_name": $definedElsewhere,
"timestamp_format": "%b %d %H:%M:%S"
}]) as $newLogSinks |
.logs.logs_collected.files.collect_list += $newLogSinks
' --args "${logPaths[#]}" <old.json >new.json && mv new.json old.json
...which correctly emits as output:
{
"logs": {
"logs_collected": {
"files": {
"collect_list": [
{
"test": "make sure this old data is retained"
},
{
"file_path": "/shared/logs/application/application1",
"log_group_name": "/aws-account/aws/ec2/syslogs",
"log_stream_name": "Other Value",
"timestamp_format": "%b %d %H:%M:%S"
},
{
"file_path": "/shared/logs/application/application2",
"log_group_name": "/aws-account/aws/ec2/syslogs",
"log_stream_name": "Other Value",
"timestamp_format": "%b %d %H:%M:%S"
},
{
"file_path": "/shared/logs/application/application3",
"log_group_name": "/aws-account/aws/ec2/syslogs",
"log_stream_name": "Other Value",
"timestamp_format": "%b %d %H:%M:%S"
}
]
}
}
}
}

shell script,how to print the newline in default position always?

i am using tput sc/rc/ed and printf '\E[n<A|B|C|D>'|printf '\E[y;xH',here two ex:
tty_esc(){ printf "\e[%s" "$1"; }
tty_cursor_locate(){ tty_esc "${2};${1}H"; }
tty_cursor_right(){ tty_esc ${1}C; }
print_center()
{
local _width=$(tput cols)
local _str=$1
local _row=$2
local _cols=$((((${_width} - ${#_str})) / 2))
tty_cursor_locate ${_cols:-100} ${_row:-1}
printf "%s\n" " ${_str} "
}
show_net_adapter()
{
local _addr _iface _count
local _origin=$1
iface_line_count=
tty_cursor_locate ${_origin:-0} 4
printf "%s\n" "Current connected adapter(s):"
for _iface in $(get_net_adapter);do
if [[ "${_iface}" != "lo" ]];then
_addr=$(get_net_addr ${_iface})
test -z "${_addr}" && continue
let _count+=1
let iface_line_count+=1
if [[ ${_count} != 1 ]];then
unset _count
printf '%s' "${tty_rever}"
fi
tty_cursor_right ${_origin:-0}
print_fill 50 ${_iface} ${_addr:--}
printf "${tty_reset}"
fi
done
print_line -s "=" ${line_origin}
}
as above, I should locate the cursor before I print something.
BTW
I use trap "myfunc" WINCH, it only works once. when I try again to change my crt. size, it doesn't work.
Not really sure what you mean by in default position always, either intentations or overwrite the text but the following function can do both.
#!/bin/sh
preent () { # $1=indent start line, $2=spaces, $3=reset cursor, $4=update (1=itself, 2=delete previous lines, 0=newline), $5=save cursor, $6=text
if text="$6""$7""$8""$9""${10}" awk -v col="$COLUMNS" -v cp="$_CURSOR_POS" -v id="$1" -v spc="$2" -v rc="$3" -v u="$4" -v sc="$5" '
BEGIN { t=ENVIRON["text"]; lt=sprintf("%d",col/4); idx=1; len=length(t); y=0; sp=sprintf("% " spc "s",""); delete A;
for(ln=1;idx<len;ln++) { if(ln==id) { col=col-spc; y=1 } bd=col-lt; f=0
for(i=idx+col-1;i>bd;i--) { c=substr(t,i,1);
if(c==" ") {
if(y) { A[ln]=sprintf("%s%s",sp,substr(t,idx,i-idx)); idx=i+1; f=1; break
} else { A[ln]=substr(t,idx,i-idx); idx=i+1; f=1; break }
} else if(c=="") {
if(y) { A[ln]=sprintf("%s%s",sp,substr(t,idx,i-idx)); idx=i; f=1; break
} else { A[ln]=substr(t,idx,i-idx); idx=i; f=1; break; }
}
}
if(!f) {
if(y) { A[ln]=sprintf("%s%s",sp,substr(t,idx,col)); idx=idx+col
} else { A[ln]=substr(t,idx,col); idx=idx+col }
}
} if(rc=="true") cp=0;
if(u=="1") { for(i=1;i<ln;i++) printf("\x1B[1A\x1B[K"); cp=cp+1-i
} else if(u=="2") { for(i=0;i<cp;i++) printf("\x1B[1A\x1B[K"); cp=0 }
for(i=1;i<ln;i++) printf("%s\n",A[i]);
if(sc=="true") { exit ln-1+cp } else { exit 0 }
}'; then _CURSOR_POS="$?"; else _CURSOR_POS="$?"; fi
}
Example 1 (Long text, 5 spaces indent, indent starts from 2nd line)
preent 2 5 'false' 0 'false' 'You never say good-bye Handong-an monghani udukoni anja Dashi saenggakhatjiman ' \
'Momchul sun optkesso Ontong kudae saenggak hal subakke omnun ' \
"Nae jashini miwo Don't you let me go Baby don't you let me down"
Output
You never say good-bye Handong-an monghani
udukoni anja Dashi saenggakhatjiman Momchul
sun optkesso Ontong kudae saenggak hal
subakke omnun Nae jashini miwo Don't you
let me go Baby don't you let me down
Example 2 (Long text with no spaces)
preent 2 5 'false' 0 'false' 'Youneversaygood-byeHandong-anmonghaniudukonianjaDashisaenggakhatjiman' \
'MomchulsunoptkessoOntongkudaesaenggakhalsubakkeomnun' \
"NaejashinimiwoDon'tyouletmegoBabydon'tyouletmedown"
Output
Youneversaygood-byeHandong-anmonghaniudukonianjaD
ashisaenggakhatjimanMomchulsunoptkessoOntong
kudaesaenggakhalsubakkeomnunNaejashinimiwoDo
n'tyouletmegoBabydon'tyouletmedown

bash replace multiline text in file, with pattern

Hi I need to edit some file but I dinĀ“t want to do it manually, I know that using sed command I can edit files using command line, but in this case, I don't know how to match the pattern to edit. for example I have this file:
(
AMI1
{
type patch; // <- relpace patch by cyclicAMI;
nFaces 1350;
startFace 2433406;
}
inlet
{
type patch;
nFaces 1125;
startFace 2434756;
}
outlet
{
type patch;
nFaces 1125;
startFace 2435881;
}
AMI2
{
type patch; // <- relpace patch by cyclicAMI;
nFaces 2850;
startFace 2440606;
}
)
And I want to edit ONLY the AMI keys to look like this:
(
AMI1
{
type cyclicAMI; // <-- Replaced
inGroups 1(cyclicAMI); // <-- Add
nFaces 1350;
startFace 2433406;
matchTolerance 0.0001; // <-- Add
transform noOrdering; // <-- Add
neighbourPatch AMI2; // <-- Add AMI2 in AMI1
}
inlet
{
type patch;
nFaces 1125;
startFace 2434756;
}
outlet
{
type patch;
nFaces 1125;
startFace 2435881;
}
AMI2
{
type cyclicAMI; // <-- Replaced
inGroups 1(cyclicAMI); // <-- Add
nFaces 2850;
startFace 2440606;
matchTolerance 0.0001; // <-- Add
transform noOrdering; // <-- Add
neighbourPatch AMI1; // <-- Add AMI1 in AMI2
}
)
Not overly elegant, but working.
state=0
while IFS= read -r line; do
case "${line// }" in
AMI[12]) state=${line##*AMI}
echo "$line";;
typepatch\;*) echo " type cyclicAMI;"
echo " inGroups 1(cyclicAMI);";;
else
echo "$line"
fi;;
\}) if [ "$state" != 0 ]; then
echo " matchTolerance 0.0001;"
echo " transform noOrdering;"
echo " neighbourPatch AMI$((3-state));"
echo " }"
state=0
else
echo " }"
fi;;
*) echo "$line"
esac
done < textfile.txt

Resources