Read line from stdin and run command on it - bash

I'm trying to get the following command to work
gcloud compute instances list --format=json --regexp .*gluster.* | jq '.[].networkInterfaces[].networkIP' | tr -d '\"' | while read i; do gcloud compute ssh --zone $ZONE ubuntu#gluster-1 -- "sudo gluster peer probe $i && cat >> peers.txt"; done
Basically the gcloud command gives:
gcloud compute instances list --format=json --regexp .*gluster.* | jq '.[].networkInterfaces[].networkIP' | tr -d '\"'
10.128.0.2
10.128.0.3
10.128.0.4
However running the above command only gives seems to only run on the first ip which is the host, giving the warning
peer probe: success. Probe on localhost not needed
And none of the other nodes get connected.
Notes:
Weirdly running the gcloud command on the second node connects to the first one, running on the third doesn't do anything at all
The peers.txt file on all the nodes except the the third have again weirdly only the two latter ips
ubuntu#gluster-1:~$ cat peers.txt
10.128.0.3
10.128.0.4
Running echo on the value in the loop gives
gcloud compute instances list --format=json --regexp .*gluster.* | jq '.[].networkInterfaces[].networkIP' | tr -d '\"' | while read i; do echo ip: $i; done
ip: 10.128.0.2
ip: 10.128.0.3
ip: 10.128.0.4

There is nothing wrong with piping into a loop (assuming you don't need the body of the loop to execute in the current shell). You don't want to use a for loop for something like this, though; see Bash FAQ 001 for more information. Use a while loop.
gcloud compute instances list --format=json --regexp .*gluster.* |
jq -r '.[].networkInterfaces[].networkIP' |
while IFS= read -r ipaddr; do
echo "$ipaddr"
done
(Note that using the -r option with jq eliminates the need to pipe the output into tr to remove the double quotes.)
The problem you may be seeing is that the command you put in the while loop also reads from standard input, which consumes data from your pipeline before read can read it. In that case, you can redirect standard input from /dev/null:
gcloud compute instances list --format=json --regexp .*gluster.* |
jq -r '.[].networkInterfaces[].networkIP' |
while IFS= read -r i; do
gcloud compute ssh --zone $ZONE ubuntu#gluster-1 \
-- "sudo gluster peer probe $i < /dev/null &&
cat >> peers.txt"
done
Or, use a process substitution to read from a different file descriptor.
while IFS= read -r i <&3; do
gcloud ...
done 3< <(gcloud compute instances .. | jq -r '...')

Got it to work with a for loop.
Also learnt that for loops aren't for piping into :)
for item in $(gcloud compute instances list --format=json --regexp .*gluster.* | jq '.[].networkInterfaces[].networkIP' | tr -d '\"'); do gcloud compute ssh --zone $ZONE ubuntu#gluster-1 -- "sudo gluster peer probe $item && echo $item >> peers.txt"; done

Related

why is jq not working inside bash variable

I have the following code
#/bin/bash
set -e
set -x
requestResponse=$(ssh jump.gcp.xxxxxxx """source dev2 spi-dev
kubectl get pods -o json | jq '.items[] |select(.metadata.name[0:3]=="fea")' | jq .status.podIP
2>&1""")
echo $requestResponse
In the above code source dev2 spi-dev means we have moved to spi-dev namespace inside dev2 cluster. kubectl get pods -o json | jq '.items[] |select(.metadata.name[0:3]=="fea")' | jq .status.podIP 2>&1""") means to print ip address of pod starting with fea. If I do manually kubectl command works. I have also tried escaping fea like \"fea\"
These triple quotes """ are not working as you expect.
Try to change it like this:
ssh jump.gcp.xxxxxxx << EOF
source dev2 spi-dev
kubectl get pods -o json | \
jq '.items[] | \
select(.metadata.name[0:3]=="fea")' | \
jq .status.podIP 2>&1
EOF

running a for loop over ssh remotely

team, I have below cord that does ssh login and stays connected. I want to run some commands in for loop but getting some syntax errors. The same exact commands work when i manually login to node and sudo bash and just copy paste.
code
read -p "specify just the list of nodes " nodes
for node in $nodes
do
ssh -q -F $HOME/.ssh/ssh_config -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -t $node.team.net \
"for line in `docker ps | grep test | awk '{print $1}'`;
do
POD_ID=$(docker inspect $line --format='{{ index .Config.Labels "io.kubernetes.pod.uid" }}')
POD_NAME=$(docker inspect $line --format='{{ index .Config.Labels "io.kubernetes.pod.name"}}')
POD_VOL="/var/lib/kubelet/pods/$POD_ID/volumes"
POD_DU=$(du -sh $POD_VOL < /dev/null)
HOSTNAME=$(hostname)
AGENT_SHA=$(docker inspect $line --format='{{ index .Config.Image }}' | cut -d ':' -f2)
STARTED_AT=$(docker inspect $line --format='{{ .State.StartedAt }}')
echo $HOSTNAME, $POD_NAME, $POD_DU, $AGENT_SHA, $STARTED_AT
done"
printf "\n"
done;
output
Your new SSH certificate is ready for use!
specify just the list of nodes node1
"docker inspect" requires at least 1 argument.
See 'docker inspect --help'.
Usage: docker inspect [OPTIONS] NAME|ID [NAME|ID...]
Return low-level information on Docker objects
"docker inspect" requires at least 1 argument.
See 'docker inspect --help'.
expected
container1 45GB
..
..
Is better send a file with script content and run. Something like:
Copy script
for a in {server1,server2,serverN};
do
scp your_script.sh root#$a:/path/to/your_script.sh
done
Exec script
for a in {server1,server2,serverN};
do
ssh root#$a "sh /path/to/your_script.sh par1 par2 parn";
done

Sorted ouput, needs to have text inserted between string

I trying to add text (predefined) between a sorted output and saved to a new file.
I'm using a curl command to gather my info.
$ curl --user XXX:1234!## "http://......"
Then using grep to find IP addresses and sorting so they only appear once.
$ curl --user XXX:1234!## "http://......" | grep -E -o -m1 '([0-9]{1,3}[\.]){3}[0-9]{1,3}' | sort -u
I need to add <my_text_predefined> ([0-9]{1,3}[\.]){3}[0-9]{1,3} <my_text_predefined> between the regex ip address and then saved to a new file.
The script below only get my the ip address
$ curl --user XXX:1234!## "http://......" | grep -E -o -m1 '([0-9]{1,3}[\.]){3}[0-9]{1,3}' | sort -u
123.12.0.12
123.56.98.76
$ curl --user some_user:password "http://...." | grep -E -o -m1 '([0-9]{1,3}[\.]){3}[0-9]{1,3}' | sort -u | sed 's/.*/<prefix> -s & <suffix>/'
So if we need print some text for each IP ... try xargs
for i in {1..100}; do echo $i; done | xargs -n1 echo "Values are:"
if based on IP you would need to take decision put in a loop
for file $(curl ...) do ...
and check $file or do something with it ...

Generating bash script arrays with elements containing spaces from commands

I have a script that logs in to a remote host to pull a directory listing to later present options to the user. It was all working perfectly, until some of the directories started having spaces in them. I have tried several syntaxes and googled the life out of this and I am now at the end of my tether. The original command was this:
SERVERDIRS=($(sshpass -p $PASS ssh -oStrictHostKeyChecking=no $USER#$SERVER ls -l --time-style="long-iso" $FROMFOLDER | egrep '^d' | awk '{print $8}'))
I first off changed this code to be able to read the spaces like this:
SERVERDIRS=($(sshpass -p $PASS ssh -oStrictHostKeyChecking=no $USER#$SERVER ls -l --time-style="long-iso" $FROMFOLDER | egrep '^d' | cut -d' ' -f8-))
However This resulted in each word being recognised as a variable. I have tried many ways to try to solve this, two of which were:
SERVERDIRS=($(sshpass -p $PASS ssh -oStrictHostKeyChecking=no $USER#$SERVER ls -d $FROMFOLDER* |rev| cut -d'/' -f1|rev|sed s/^/\"/g|sed s/$/\"/g))
SERVERDIRS=($(sshpass -p $PASS ssh -oStrictHostKeyChecking=no $USER#$SERVER ls -d $FROMFOLDER* |rev| cut -d'/' -f1|rev|sed 's/ /\\ /g'))
SERVERDIRS=(`sshpass -p $PASS ssh -oStrictHostKeyChecking=no $USER#$SERVER ls -d $FROMFOLDER* |rev| cut -d'/' -f1|rev|sed 's/ /\\ /g'`)
How can I resolve these directories in to separate elements correctly?
If you're trying to read one array value per line instead of space-separated, then $() syntax won't help. Try readarray (Bash 4):
readarray SERVERDIRS < <(sshpass -p $PASS ssh -oStrictHostKeyChecking=no $USER#$SERVER ls -l --time-style="long-iso" $FROMFOLDER | egrep '^d' | cut -d' ' -f8-)
or assign IFS and read with -d, -r, and -a set:
IFS=$'\n' read -d '' -r -a SERVERDIRS < <(sshpass -p $PASS ssh -oStrictHostKeyChecking=no $USER#$SERVER ls -l --time-style="long-iso" $FROMFOLDER | egrep '^d' | cut -d' ' -f8-)
or, really, any other answer to this SO question.
If you're unfamiliar with <() syntax, it's known as process substitution and will allow your variable to be set in your current environment rather than the instantly-discarded subshell that a pipe would create.
Bear in mind that this process is a little dangerous; filenames can also contain newlines, so it's usually much preferred to use find ... -print0.
If you only need to list directories, try this
ls -d /usr/local/src/*/
or
ls -d /path/to/your/directory/*/
You can then loop through all directories
#!/bin/bash
aa=`ls -d /usr/local/src/*/`
for dir in "${aa}[#]"
do
echo "$dir"
done
This works if dir names contain spaces.

Systemd string escaping

If I run this command
/bin/bash -c 'while true;do /usr/bin/etcdctl set my-container "{\"host\": \"1\", \"port\": $(/usr/bin/docker port my-container 5000 | cut -d":" -f2)}" --ttl 60;sleep 45;done'
I get back from etcd what I expect {"host":"1", "port":49155}
But if I put it in a systemd file
ExecStart=/bin/bash -c 'while true;do /usr/bin/etcdctl set my-container "{\"host\": \"1\", \"port\": $(/usr/bin/docker port my-container 5000 | cut -d":" -f2)}" --ttl 60;sleep 45;done'
I get back {host:1, port:49155}
Any idea of why the escaping is different inside of the file? How can I fix it? Thanks!!
systemd-escape '\"a fun thing"\'
output: \x5c\x22a\x20fun\x20thing\x22\x5c
[Service]
ExecStart=/bin/sh -c 'echo "\x5c\x22a\x20fun\x20thing\x22\x5c"'
will print a fun thing
Systemd is doing isn't like bash as you now know, hence the escaping problem. In fact, systemd removes single and double quotes after parsing them. That fact is right out of the documentation (I went thru this too, then read :D).
The solution, call a script that echo back that info you need (with escaped quotes) if your purpose allows that.
In short -- it's different because systemd does its own string-splitting, unescaping and expansion, and the logic it uses isn't POSIX-compliant.
You can still do what you want, but you'll need more backslashes:
ExecStart=/bin/bash -c '\
while :; do \
port=$(/usr/bin/docker port my-container 5000 | cut -d: -f2); \
/usr/bin/etcdctl set my-container "{\\\"host\\\": \\\"1\\\", \\\"port\\\": $port}" --ttl 60; \
sleep 45; \
done'
Note the use of \\\" for every literal " character in the desired output.
By the way -- personally, I advise against trying to generate JSON through string concatenation -- it's prone to injection vulnerabilities (if someone could put content of their choice in the output of the docker port command, they could potentially insert other key/value pairs into your data by having , "evil": true be in the port variable). This class of issues is avoided by using jq:
ExecStart=/bin/bash -c '\
while :; do \
port=$(/usr/bin/docker port my-container 5000 | cut -d: -f2); \
json=$(jq -nc \
--arg host 1 \
--arg port "$port" \
'{} | .host=$host | .port=($port | tonumber)'); \
/usr/bin/etcdctl set my-container "$json" --ttl 60; \
sleep 45; \
done'
As a happy side effect, the above avoids needing any literal double-quote characters (the only ones used are syntactic to the copy of sh), so we don't need any backslashes to be passed through from systemd to the shell.

Resources