Is there a way to add a single line (or better yet a few lines) of new text to the very first line of a file with Puppet? (and have it do it just once obviously)
The background is, I am managing certain lines in the file, but I would like to put a one-time comment at the top of the file so its clear parts of this file are "managed".
I don't think you can do that with standard puppet resources such as file_line or augeas. What you could do is use an exec fx [edited]:
$file = /somefile,
$text = 'some text'
) {
exec { "add ${text} to ${file}":
command => "sed -i '1s/^/${text}\\n/' '${file}'",
unless => "grep '${text}' '${file}'",
path => ['/bin'],
}
}
See How do I add text to the beginning of a file in Bash? for other examples using bash.
[original posted syntax]
The example is updated with suggesting from [anonymous] to support spaces in filename and double escaped new line. Kept original syntax below for reference, since I haven't tested the double escaping of newline.
$file = /somefile,
$text = 'some text'
) {
exec { "add ${text} to ${file}":
command => "sed -i '1s/^/${text}\n/' ${file}",
unless => "grep '${text}' ${file}",
path => ['bin'],
}
}
Related
I am writing a bash script to find if a code block starting with
if (isset($conf['memcache_servers'])) { exists in a file?
If true, then I need to return the whole if block.
How to do that?
Code block return example:
if (isset($conf['memcache_servers'])) {
$conf['cache_backends'][] = '.memcache.inc';
$conf['cache_default_class'] = 'MemCache';
$conf['cache_class_cache_form'] = 'DatabaseCache';
}
You can use sed to do this. From a bash command line, run this:
sed -n "/if (isset(\$conf\['memcache_servers'\]))/,/}/p" inputFile
This uses range option /pattern1/,/pattern2/ from sed, and p to print everything between and including the if...{ and } lines.
Here, I have used double quotes to express the sed script because the first pattern includes single quotes'. he sqaure-brackets need to be escaped as well. \[ and \].
If I do
comment () { sed -i "/$1/s/^/$2 /g" $3 }
comment /dev/sysmsg '#' /tmp/1
comment '*.err' '#' /tmp/1
with input file
*.err;kern.notice;auth.notice /dev/sysmsg
fff
ff
then it breaks, as / is used in sed as separator, and * also be treated as a regex.
Question
Is there a way to make it robust, so the input string in $1 can contain whatever I want? Or would I need to move to Perl?
Sure, use bash parameter substitution to escape the troublesome slash character:
comment () { sed -i "/${1//\//\\/}/ s/^/${2//\//\\/} /" "$3"; }
Notes:
You need to protect slashes in both the pattern and the replacement parts.
if your search is anchored, using "g" is pointless because the pattern can match at most once.
quote all your variables: if the filename contains a space, your code breaks.
one line functions require a semicolon before the closing brace.
Demo
$ cat file
test/1/
test/2/
test/3/
$ comment 'test/2' '//' file
$ cat file
test/1/
// test/2/
test/3/
I realized I'm not escaping regex special characters. The safest way is to escape any non-alphanumeric characters:
comment () {
local pattern=$(sed 's/[^[:alnum:]]/\\&/g' <<<"$1")
local replace=${2//\//\\/}
local file=$3
sed -i "/$pattern/ s/^/$replace /" "$file"
}
But since you want to do plain text matching, sed is probably not the best tool:
comment() {
perl -i -pse '$_ = "$leader $_" if index($_, $search) > -1' -- \
-search="$1" \
-leader="$2" \
"$3"
}
Best to avoid generating code from a shell script.
comment () {
perl -i -pe'BEGIN { ($s,$r)=splice(#ARGV,0,2) } $_ = "$r $_" if /\Q$s/' -- "$#"
}
or
comment () {
s="$1" r="$2" perl -i -pe'$_ = "$ENV{r} $_" if /\Q$ENV{s}/' -- "$3"
}
or
comment () {
perl -i -spe'$_ = "$r $_" if /\Q$s/' -- -s="$1" -r="$2" -- "$3"
}
Supports:
Arbitrary text for the search string (including characters that might normally be special in regex patterns, such as *). This is achieved by using quotemeta (as \Q) to convert the text into a regex pattern that matches that text.
Arbitrary file names (including those that contain spaces or start with -), thanks to proper quoting and the use of --.
I am iterating a file then there is a string for which I have to search for and from that string I have to replace a sub string with another string. Can anyone help me to solve this problem.
I tried like this.
while(<FH>)
{
if($_ =~ /AndroidAPIEventLogging=false/i)
{
if($& =~ s/false/True/)
{
print("Changed successfully\n");
}
}
}
Now it is showing that it can perform only read operations. I tried by opening the file in each possible mode.
Match and substitute is some kind of perl anti-pattern, as you're matching (often same strings) two times, so back to your question
while (<FH>) {
# everything before '\K' is not replaced (positive look behind)
if (s/AndroidAPIEventLogging=\Kfalse/True/i) { # $_ =~
print("Changed successfully\n");
}
}
You can do that using -i option via Perl one-liner
perl -i -pe 's/AndroidAPIEventLogging=false/AndroidAPIEventLogging=true/i' file1 file2...
As an alternative way, take a look at Tie::File. It seems to be designed for quick in-place file edits.
I want to edit a file in place (substitute a column value) based on a value in another column in same file. I don't want to redirect the output after substitution to another file but rather would want to edit the file in place. Specifically need this because file which needs to be edited will be accessed by no of programs simultaneously and in-place editing is must for this
I tried code below. but this is again writing to input file line by line
#!/usr/bin/perl -w
open(FILEIN, "/dummy/chaat/h2") || die("Cannot open file");
#file = <FILEIN>;
seek FILEIN, 0, 0;
foreach $file (#file) {
#field = split(/\|/, $file);
print $field[8] . "\n";
if ($field[8] eq "ROBERT") {
$file =~ s/JAMES/FUDIK/g;
print FILEIN $file;
}
}
My sample records are as shown below. Here I want to do inline editing of this. if field 8 is ROBERT then substitute JAMES with FUDIK in field 7
Change|sinmg|ewfewf|ewfew|def|fdfd|JAMES|rewr|ROBERT|dsf|fe
Change|sinmg|ewfewf|ewfew|def|JAMES|fewf|rewr|BEASLEY|dsf|fe
I would appreciate any help on this
This is a perl one-liner:
perl -F'\|' -i -lape 'if ($F[8] eq "ROBERT") { s/JAMES/FUDIK/g }' /dummy/chaat/h2
The -i option will do the in-place edit. You may wish to restrict the substitution to avoid partial matches, e.g.:
s/(?<=\|)JAMES(?=\|)/FUDIK/g
Or you can use the -n switch instead of -p and do
s/JAMES/FUDIK/g for #F } print join "|", #F;
You should be aware that this in-place edit is in fact writing a new file, and copying over the old. If you want to avoid this, you need to do some serious juggling, and probably lock the file during writing.
I'm writing a Perl script, and I'd like a way to have the user enter a file or a file containing a list of files in $ARGV[0].
The current way that I'm doing it is to check if the filename starts with an #, if it does, then I treat that file as a list of filenames.
This is definitely not the ideal way to do it, because I've noticed that # is a special character in bash (What does it do by the way? I've only seen it used in $# in bash).
You can specify additional parameter on your command line to treat it differenly e.g.
perl script.pl file
for reading file's content, or
perl script.pl -l file
for reading list of files from file.
You can use getopt module for easier parsing of input arguments.
First, you could use your shell to grab the list for you:
perl script.pl <( cat list )
If you don't want to do that, perhaps because you are running against the maximum command line length, you could use the following before you use #ARGV or ARGV (including <>):
#ARGV = map {
if (my $qfn = /^\#(.*)/s) {
if (!open(my $fh, '<', $qfn)) {
chomp( my #args = <$fh> );
#args
} else {
warn("Can't open $qfn: $!\n");
()
}
} else {
$_
}
} #ARGV;
Keep in mind that you'll have unintended side effects if you have a file whose name starts with "#".
'#' is special in Perl, so you need to escape it in your Perl strings--unless you use the non-interpolating string types of 'a non-interpolating $string' or q(another non-interpolating $string) or you need to escape it, like so
if ( $arg =~ /^\#/ ) {
...
}
Interpolating delimiters are any of the following:
"..." or qq/.../
`...` or qx/.../
/.../ or qr/.../
For all those, you will have to escape any literal #.
Otherwise, a filename starting with a # has pretty good precedence in command line arguments.