Shell execute grep command within sed replacement not as expected - linux

I am working on some deployment script for Kubernetes, and want to execute a single command to replace some "Template Variables" in yaml file using sed.
I have the following (example, shortened yaml file) input file where i want to do the replacements:
input.txt
CONTAINER_ADDITIONAL
spec:
selector:
app: CONTAINER_NAME
type: NodePort
ports:
- protocol: TCP
port: EXPOSED_PORT
Also, I've got the following Dockerfile where i want to get the EXPOSE port number from to be inserted into the EXPOSED_PORT:
Dockerfile
FROM node:latest
EXPOSE 3000
WORKDIR /app
I have now tried to use different approaches to get the port number 3000 (from Dockerfile) inserted into the EXPOSED_PORT
(CONTAINER_NAME and CONTAINER_ADDITIONAL are working, they are in the example file for completenes with the command below).
The following commands can be executed directly in the shell and give the wanted result (3000):
cat Dockerfile | grep EXPOSE | cut -d" " -f2 -> there may be still the \n
cat Dockerfile | grep EXPOSE | cut -d" " -f2 | tr -d "\n" -> previously mentionned \n removed
grep EXPOSE Dockerfile | cut -d" " -f2 -> with \n
grep EXPOSE Dockerfile | cut -d" " -f2 | tr -d "\n" -> without \n
grep EXPOSE Dockerfile | awk '{print $2}' -> with \n, uses single quote - not an option?
grep EXPOSE Dockerfile|tr -d -c 0-9 -> without \n, not prefered (when situation would exist of multiple port numbers separated by spaces)
grep -Po "(?<=^EXPOSE )\w*$" Dockerfile -> with \n, not prefered (multiple port numbers)
grep -Po "(?<=^EXPOSE )\w*$" Dockerfile | tr -d "\n" -> without \n
HOWEVER:
sed -e 's#CONTAINER_NAME#some-container-name#g;s#CONTAINER_ADDITIONAL#cat some_config.txt#e;s#EXPOSED_PORT#grep EXPOSE Dockerfile | cut -d" " -f2 | tr -d "\n"#e' input.txt
Does not work for the EXPOSED_PORT. The other two variables CONTAINER_NAME and CONTAINER_ADDITIONAL work (the cat gets executed, content of some_config.txt is being put in there)
No matter which of the above mentioned commands that are working and giving the correct result directly in shell, they do not work when executed in sed (the awk for sure not, because of single quotes).
The output I get is:
inserted_content_of: some_config.txt
some_more_inserted_content_from: some_config.txt
spec:
selector:
app: some-container-name
type: NodePort
ports:
- protocol: TCP
sh: 1: port:: not found
The expected output that i want to have:
inserted_content_of: some_config.txt
some_more_inserted_content_from: some_config.txt
spec:
selector:
app: some-container-name
type: NodePort
ports:
- protocol: TCP
port: 3000
Is there anything that I am doing wrong with the sed command?
Is there some explanation what goes wrong?
How can i solve this issue?

I would think, that
grep EXPOSE input.txt | cut -d" " -f2
is a good way to get the portnumber (the \n is not appended when used in another command). Perhaps you should save it first before your next step.
portnumber=$(grep EXPOSE input.txt | cut -d" " -f2)
echo "The portnumber will be [${portnumber}]."
I will replace your grep command with echo "4444", showing the problem with your nested command.
With #e you ask sed to execute the resulting string after processing s#EXPOSED_PORT#echo "4444"#. The line with EXPOSED_PORT is
port: EXPOSED_PORT
So sed is trying to execute port echo "4444", and complains about the command port.
When you want to use the #e, you should use something like
sed -r 's#(.*)(EXPOSED_PORT)(.*)#echo "\1$(echo "4444")\3"#e' input.txt
And you thought it would be easy? Try like this:
sed 's#EXPOSED_PORT#'${portnumber}'#' input.txt
# or when you really want to squeeze your command into this line
sed 's#EXPOSED_PORT#'$(echo "4444")'#' input.txt
Or look at awk:
awk -v port=${portnumber} '/EXPOSED_PORT/ {$2=port} 1' input.txt
# or nested
awk -v port=$(echo "4444") '/EXPOSED_PORT/ {$2=port} 1' input.txt

The issue is that you are not changing the whole line when changing the port but rather just a section of the line. Because the leading "port:" has been left, sed tries to execute this along with the grep command and hence the error. To overcome, this, search and replace the whole line and so:
sed -e 's#CONTAINER_NAME#some-container-name#g;s#CONTAINER_ADDITIONAL#cat some_config.txt#e;s#^.*EXPOSED_PORT#echo " port: $(grep EXPOSE input.txt | cut -d" " -f2 | tr -d "\n")"#e' input.txt
Echo out the leading port: along with the expanded grep command.

Related

How to run a command in sed in Linux?

sed -i 's/1.1.1.1/ `hostname -I | cut -f1 -d " "`/g' file.txt
Not able to overwrite IP address using sed command in a given file. How to run this (hostname -I | cut -f1 -d " ") command with sed command?
This might work for you (GNU sed):
sed -i '/1\.1\.1\.1/{s//$(hostname -I | cut -f1 -d " ")/;s/.*/echo "&"/e}' file
Place the commands between $(...) and then echo the line using the evaluate flag of the substitution command.
Just use a variable.
hn=$(hostname -I | cut -f1 -d" ") && sed -i "s/1\.1\.1\.1/$hn/g" file
Sed's e and s///e work on the entire pattern space, so it's just more trouble than it's worth.

Get specific output from grep [duplicate]

This question already has answers here:
How to get the second column from command output?
(8 answers)
Closed 3 years ago.
I am trying to get a specific output using grep but I am unable to do so.
Here is my grep command :
crictl inspect 47aaecb541688accf37840108cc0d19b39b84f8337740edf2ca7e5e81a24328e | grep "io.kubernetes.pod.namespace"
The output of the above command is "io.kubernetes.pod.namespace": "kube-system",
I even tried
crictl inspect 47aaecb541688accf37840108cc0d19b39b84f8337740edf2ca7e5e81a24328e | grep -Po '(?<="io.kubernetes.pod.namespace": ").*' but the output i got is kube-system",
I just want the value i.e just kube-system
How do I modify my grep command.
Thanks for the help
Using grep
We need to make just one small change to the grep -P command. Your command was:
$ echo '"io.kubernetes.pod.namespace": "kube-system",' | grep -Po '(?<="io.kubernetes.pod.namespace": ").*'
kube-system",
We just need to replace .* (which matches everything to the end of the line) with [^"]* with matches everything up to but not including the first ":
$ echo '"io.kubernetes.pod.namespace": "kube-system",' | grep -Po '(?<="io.kubernetes.pod.namespace": ")[^"]*'
kube-system
Or, using your crictl command:
crictl inspect 47aaecb541688accf37840108cc0d19b39b84f8337740edf2ca7e5e81a24328e | grep -Po '(?<="io.kubernetes.pod.namespace": ")[^"]*'
Using sed
$ echo '"io.kubernetes.pod.namespace": "kube-system",' | sed -n '/"io.kubernetes.pod.namespace"/{s/.*": "//; s/".*//p}'
kube-system
How it works:
-n tells sed not to print unless we explicitly ask it to.
/"io.kubernetes.pod.namespace"/{...} selects only those lines that contain "io.kubernetes.pod.namespace" and performs the commands in braces on them.
s/.*": "// removes everything from the beginning of the line to the last occurrence of ": ".
s/".*//p removes everything from the first remaining " to the end of the line and prints the result.

Parsing nmap -oG output using sed

I have a logfile
...
Host: 111.222.121.123 (111.222.121.123.deploy.static.akamaitechnologies.com) Ports: 80/open/tcp//http//AkamaiGHost (Akamai's HTTP Acceleration|Mirror service)/, 443/open/tcp//ssl|http//AkamaiGHost (Akamai's HTTP Acceleration|Mirror service)/
Host: 1.2.3.4 () Ports: 80/open/tcp//http//cloudflare/, 443/open/tcp//ssl|https//cloudflare/, 2052/open/tcp//clearvisn?///, 2053/open/tcp//ssl|http//nginx/, 2082/open/tcp//infowave?///, 2083/open/tcp//ssl|http//nginx/, 2086/open/tcp//gnunet?///, 2087/open/tcp//ssl|http//nginx/, 2095/open/tcp//nbx-ser?///, 2096/open/tcp//ssl|http//nginx/, 8080/open/tcp//http-proxy//cloudflare/, 8443/open/tcp//ssl|https-alt//cloudflare/, 8880/open/tcp//cddbp-alt?///
Host: 2.3.4.5 (a104-96-1-61.deploy.static.akamaitechnologies.com) Ports: 53/open/tcp//domain//(unknown banner: 29571.61)/
...
I need to extract and convert IPs and http ports to the following format
1.2.3.4:80,443,2083
There are just two types of port fields in the logfile
80/open/tcp//http
2083/open/tcp//ssl|http
Tried to use sed but without success. I ended up with this dysfunctional command
cat ../host_ports.txt | sed -rn 's/Host: ([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}).*?([0-9]{1,5}\/open\/tcp\/\/http|[0-9]{1,5}\/open\/tcp\/\/ssl\|http).*/\1 \2/p'
First handle the repeating ports, and next replace Host/Port to the desired format.
sed -r 's/(Ports:|,) ([0-9]*)[^,]*/\1\2/g;s/Host: ([^ ]*).*Ports:/\1:/' ../host_ports.txt
EDIT:
First I gave all ports of a line with http somewhere, now limit the result to ports with http in its description.
sed -nr 's/Ports: /, /;
s/, ([0-9]*)[^,]*http[^,]*/,\1/g;
s/,[^,]*\/[^,]*//g;
s/Host: ([^ ]*)[^,]*,/\1:/p' ../host_ports.txt
This script will do it for you, and you don't need sed :
#!/bin/bash
while read -r line; do
if echo $line | grep -q "http"; then
host=$(echo "$line" | grep -Po '(?<=^Host: )[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+')
ports=$(echo "$line" | grep -Po '[0-9]*((?=\/open\/tcp\/\/http)|(?=\/open\/tcp\/\/ssl\|http))' | tr '\n' ',')
echo "$host:${ports:0:-1}"
fi
done < ../log
The first grep will catch the IP address, with the help of Look behind. the -P is to use perl like regex, and the -o is to output only the matching string
The second regex is much like the first, but uses look after instead of look behind. It will only capture ports which are followed by /open/tcp//http or /open/tcp//ssl|http. The tr right after will replace newlines with commas.
the ${ports:0:-1} is just to eliminate the trailing comma.
Hope this helps!

Combining two bash commands

If found this code
host raspberrypi | grep 'address' | cut -d' ' -f4
which gives pi Ip address
and this
wget --post-data="PiIP=1.2.3.4" http://dweet.io/dweet/for/cycy42
which sends 1.2.3.4 off to dweet.io stream
How can I get the output from 1st to replace the 1.2.3.4 in second please?
Save the output of the first command in a variable:
ip=$(host raspberrypi | grep 'address' | cut -d' ' -f4)
wget --post-data="PiIP=$ip" http://dweet.io/dweet/for/cycy42
Btw, if your raspberrypi is running raspbian,
then a much cleaner way to get the IP address:
hostname -I
Simplifying the commands to:
ip=$(hostname -I)
wget --post-data="PiIP=$ip" http://dweet.io/dweet/for/cycy42
Making that a one-liner:
wget --post-data="PiIP=$(hostname -I)" http://dweet.io/dweet/for/cycy42
UPDATE
So it seems hostname -I gives a bit different output for you.
You can use this then:
ip=$(hostname -I | awk '{print $1}')
To make it a one-liner, you can insert this into the second line just like I did in the earlier example.

Returning Numbers From Text File - BASH

I've a command getting the current SVN Revision and storing it in a file, is there anyway I can select the "53413" from the file to use elsewhere?
Revision: 53413
Thanks
echo "Revision: 53413" | cut -d " " -f2
cut usage: Using space as delimiter, select the second field.
This is a bit more precise, in case filename contains more than one line of data:
rev=`awk '$1=="Revision:"{print $2}' <filename>`
Then, you can use the ${rev} elsewhere in your bash script.
You could use grep:
echo "Revision: 53413" | grep -o -P "\d+"
Or if your file has more lines you could use:
cat file | grep Revision | grep -o -P "\d+"
With file data containing
dddd 2345
try following lines
$ REV=`cat data| awk '{print $2}' `
$ echo $REV
Output is
2345

Resources