sed command only to exectue on first instance - sed

I am searching for a particular string, and appending a series of lines following it. The sed command i have now is:
sed -i "
/CLIENTVERSION/ {
n
a\define service{
a\ use generic-service
a\ host_name $var_hostname
a\ service_description NSCLient++ Version
a\ check_command check_nt!CLIENTVERSION
a\ }
}" windows.cfg;
The windows.cfg file contains service definitions for specific hosts. ( SBS and Test1 hosts are already in the file, and Test2 is the output after running my command. My output is:
define service{
use generic-service
host_name sbs
service_description NSClient++ Version
check_command check_nt!CLIENTVERSION
}
define service{
use generic-service
host_name Test2
service_description NSCLient++ Version
check_command check_nt!CLIENTVERSION
}
define service{
use generic-service
host_name Test
service_description NSCLient++ Version
check_command check_nt!CLIENTVERSION
}
define service{
use generic-service
host_name Test2
service_description NSCLient++ Version
check_command check_nt!CLIENTVERSION
}
And I want:
define service{
use generic-service
host_name sbs
service_description NSClient++ Version
check_command check_nt!CLIENTVERSION
}
define service{
use generic-service
host_name Test2
service_description NSCLient++ Version
check_command check_nt!CLIENTVERSION
}
define service{
use generic-service
host_name Test
service_description NSCLient++ Version
check_command check_nt!CLIENTVERSION
}
I thought that the /g option did this, but i haven't instituted it and am uncertain as to why it is adding "Test2" service definition twice.

You might find awk easier to work with. Using GNU awk for gensub() and multi-char RS:
$ cat tst.sh
#!/bin/env bash
infile="$1"
var_hostname="Test2"
awk -v RS='^$' -v ORS= '
NR==FNR { rec=$0; next }
{ print gensub(/CLIENTVERSION\n[^\n]+\n/,"&"rec,1) }
' - "$infile" <<!
define service{
use generic-service
host_name $var_hostname
service_description NSCLient++ Version
check_command check_nt!CLIENTVERSION
}
!
.
$ ./tst.sh file
define service{
use generic-service
host_name sbs
service_description NSClient++ Version
check_command check_nt!CLIENTVERSION
}
define service{
use generic-service
host_name Test2
service_description NSCLient++ Version
check_command check_nt!CLIENTVERSION
}
define service{
use generic-service
host_name Test
service_description NSCLient++ Version
check_command check_nt!CLIENTVERSION
}
Just change awk to awk -i inplace for inplace editing (like `sed -i) if you like. The above was run on this input file:
$ cat file
define service{
use generic-service
host_name sbs
service_description NSClient++ Version
check_command check_nt!CLIENTVERSION
}
define service{
use generic-service
host_name Test
service_description NSCLient++ Version
check_command check_nt!CLIENTVERSION
}

The approach borrowed from this answer works here:
sed -i "
/CLIENTVERSION/ {
n
a\define service{
a\ use generic-service
a\ host_name $var_hostname
a\ service_description NSCLient++ Version
a\ }
:a;n;ba}" windows.cfg
The difference is in the last line of the script, which puts it into a loop that silently reads the rest of the file without any processing.

Related

Get full call of Perl script with parameters

How can I get the file name and input parameters of the script into a variable?
So it should look like this:
# Start script as such: ./myscript.pl -d -s server1.domain
#!/usr/bin/perl
use strict;
use warnings;
my $call = some_command;
print $call; # Output: myscript.pl -d -s server1.domain
# OR ./myscript.pl -d -s server1.domain
# OR /path/to/myscript.pl -d -s server1.domain
Tried doing this with __FILE__ and $0 but I can't seem to get the input parameters in the variable.
I'm running v5.10.1 on a AIX machine.
The program and its args are found in $0 and #ARGV respectively.
You can use String::ShellQuote's shell_quote to form a command line from them.

sed script not executing in a Vagrantfile

I can't for the life of me get sed working with vagrant provisioning. I want to make a inline change to /etc/hosts.
I've verified that the sed command works when run in the shell.
Here is my Vagrantfile:
# vi: set ft=ruby :
########### Global Config ###########
machines = ["admin2"]
num_hdd_per_osd = 3
vagrant_box = %q{bento/ubuntu-18.04}
#####################################
machines.each do |machine|
Vagrant.configure("2") do |config|
config.vm.define machine do |node| #name vagrant uses to reference this VM
node.vm.box = vagrant_box
node.vm.hostname = machine
node.vm.network "private_network", ip: "192.168.0.#{ machines.index(machine) + 10}"
node.vm.provider "virtualbox" do |vb|
# Display the VirtualBox GUI when booting the machine
vb.gui = false
vb.name = machine # name virtualbox uses to refer to this vm
# Customize the amount of memory on the VM:
vb.memory = "1048"
# Core Count
vb.cpus = "2"
end
if node.vm.hostname.include? "admin"
node.vm.provision "shell", inline: <<-SHELL
sed -i.bak -e 's,\\(127\\.0\\.0\\.1[[:space:]]*localhost\\),\\1aa,' /etc/hosts
SHELL
end
end
end
end
I should see /etc/hosts changed to 127.0.0.1 localhostaa but it is unchanged.
What is wrong?
EDIT: I updated the code with the suggestion from Alex below. It now uses inline: <<-SHELL and escaped ALL escapes (so double escape). It Works!
The problem there is your Vagrantfile is Ruby code, and your sed script is inside a Ruby here string.
If you try this simplified Ruby script:
# test.rb
puts <<-SHELL
sudo sed -i.bak -e 's,\(127\.0\.0\.1[[:space:]]*localhost\),\1aa,' /etc/host
SHELL
You may see the problem:
▶ ruby test.rb
sudo sed -i.bak -e 's,(127.0.0.1[[:space:]]*localhost),aa,' /etc/host
That is, the \1 and other \ have been interpreted by Ruby prior to interpolation in the here string.
The best option for you is to use the <<'SHELL' notation, similar to what you would do in Bash:
node.vm.provision "shell", inline: <<-'SHELL'
sed -i.bak -e 's,\(127\.0\.0\.1[[:space:]]*localhost\),\1aa,' /etc/hosts
SHELL
The other option would be to escape the backslash in \1. Also, note that, as far as I can tell, the call to sudo is not required there either.
If, however, you need to interpolate a string in this script, you could do something like this:
# test.rb
mystring = 'aa'
$script = "sed -i.bak -e '" +
's,\(127\.0\.0\.1[[:space:]]*localhost\),\1' + "#{mystring},' /etc/hosts"
And then in your provisioner:
node.vm.provision "shell", inline: $script
See also this related answer.

Use colon as filename separator in zsh tab completion

I have programs which take filename arguments as
scp:path/to/file.ext
ark:/abs/path/to/file.ext
Is it possible make zsh complete filenames after some keywords followed by colon?
So far, I found how to do it in bash: add : to the COMP_WORDBREAKS variable.
Thanks to Gilles, I manage to work like this.
$ cat ~/.zshrc
...
function aftercolon() {
if compset -P 1 '*:'; then
_files "$expl[#]"
else
_files "$expl[#]"
fi
}
autoload -Uz compinit
compinit
compdef aftercolon hello olleh
Now in commands hello and olleh, completion after : works as expected.
I think there might be better way since:
I had to odd if/else since the commands also take filename without prefix.
And since I have many commands take this kind of argument, I need to add names of every commands
Call compset -P 1 '*:' to remove everything up to the first colon, then _files to complete what comes after the colon. For example, if the file name completions don't depend on the part before the colon:
if compset -P 1 '*:'; then
_files "$expl[#]"
else
compadd "$expl[#]" -S : ark scp
fi
If the file name completions depend on the part before the colon, save that from $PREFIX first.
if [[ $PREFIX = *:* ]]; then
local domain=${PREFIX%%:*}
compset -P 1 '*:'
case $domain in
ark) _files "$expl[#]" -g "*.((tar|cpio)(|.gz|.xz|.bz2)|tgz|zip|rar|7z)";;
*) _files "$expl[#]";;
esac
else
compadd "$expl[#]" -S : ark scp
fi

How to tell if my program is being piped to another (Perl)

"ls" behaves differently when its output is being piped:
> ls ???
bar foo
> ls ??? | cat
bar
foo
How does it know, and how would I do this in Perl?
In Perl, the -t file test operator indicates whether a filehandle
(including STDIN) is connected to a terminal.
There is also the -p test operator to indicate whether a filehandle
is attached to a pipe.
$ perl -e 'printf "term:%d, pipe:%d\n", -t STDIN, -p STDIN'
term:1, pipe:0
$ perl -e 'printf "term:%d, pipe:%d\n", -t STDIN, -p STDIN' < /tmp/foo
term:0, pipe:0
$ echo foo | perl -e 'printf "term:%d, pipe:%d\n", -t STDIN, -p STDIN'
term:0, pipe:1
File test operator documentation at perldoc -f -X.
use IO::Interactive qw(is_interactive);
is_interactive() or warn "Being piped\n";

Can't find the process after invoking this Perl script in shell

t:
#!/usr/bin/perl
exec("perl -Ilib -d" . $ARGV[0]);
It's invoked as t perl_script.
But after that I can't find it by ps,and can't terminate it by ^C
What's wrong ?
http://perldoc.perl.org/functions/exec.html
You're exec'ing perl with the args and perl_script you pass in. This means the current script t ceases to exist and is replaced by perl -Ilib -dperl_script.
The process you're looking for with ps will be the one you passed in (perl_script)
Edit for comment from OP below:
The actual process is perl since that's what you exec'd, but you can certainly find it via the perl_script you passed in using grep:
$ ps -ef |grep perl_script
broach 13039 2264 0 01:08 pts/0 00:00:00 perl -Ilib -dperl_script
Do you need to include a space after -d? Otherwise you are exec'ing
perl -Ilib -dperl_script
instead of
perl -Ilib -d perl_script
Cleaner still:
exec("perl","-Ilib","-d",$ARGV[0]);
exec($^X, "-Ilib", "-d", $ARGV[0]);