¿Cambiar un valor en un file de configuration o agregar la configuration si no existe?

Al modificar files de configuration desde la command-line, a menudo quiero encontrar la configuration en el file de configuration y modificar esa línea si existe esa configuration. Si esa configuration no existe, quiero agregarla al final del file.

Termino haciendo algo como:

if [ `grep -c '^setting=' example.conf` == 0 ] then echo "setting=value" >> example.conf else sed -i 's/^setting=.*/setting=value/g' example.conf fi 

Que parece una gran cantidad de código para algo tan simple. Esto ni siquiera hace cosas básicas como comprobar que el file de configuration ya finaliza en una nueva línea antes de anexarlo. Seguramente hay una utilidad que hace esto, o un command más simple que puedo usar.

La lógica extra podría manejarse con awk .

 BEGIN { FS = OFS = "=" } $1 == "setting" { $2 = "value"; found=1 } {print} END { if (!found) { print "setting=value" } 

Si la propiedad no se encuentra al final, entonces no se establecerá y la cláusula END agregará la nueva línea de configuration. El FS=OFS= garantiza el mismo formatting y la printing siempre enviará una nueva línea (ORS) que incluye la última línea. Las líneas en blanco y los comentarios se pasarán sin cambios.

Aquí hay un guión Perl confuso que acabo de escribir y que voy a poner en mi path:

  • Puede trabajar con varios files en una sola invocación
  • Puede modificar múltiples valores de configuration en cada file en una sola invocación
  • El separador se puede especificar (con --separator )
  • Opción para ser liberal sobre el espacio en blanco alnetworkingedor de los nombres

 Usage: confset <options> name1=value1 name2=value2 file1.conf file2.conf Options: -s --separator <value> What comes between names and values (default =) -w --whitespace <true|false> Allow space around names and values (default false) 

Entonces, para manejar el caso que delineé en la pregunta, lo llamaría con:

  confset example.conf setting=value 

Aquí está el guión:

 #!/usr/bin/perl use strict; my $scriptname = $0; my $separator = '='; my $whitespace = 0; my @files = (); my @namevalues = (); # read in the command line arguments for (my $i=0; $i<scalar(@ARGV); $i++){ my $arg = @ARGV[$i]; if ($arg =~ /^-/){ &printHelp(*STDOUT, 0) if ($arg eq "-h" or $arg eq "--help"); &printHelp(*STDERR, 1) if ($i+1 >= scalar(@ARGV)); my $opt = @ARGV[++$i]; if ($arg eq "-s" or $arg eq "--separator"){ $separator = $opt; } elsif ($arg eq "-w" or $arg eq "--whitespace"){ $whitespace = 0; $whitespace = 1 if ($opt =~ /1|t|y/); } else { &printHelp(*STDERR, 1); } } elsif ( -e $arg){ push(@files, $arg); } else { push(@namevalues, $arg); } } # check the validity of the command line arguments if (scalar(@files) == 0){ print STDERR "ERROR: No files specified\n"; printHelp(*STDERR, 1); } if (scalar(@namevalues) == 0){ print STDERR "ERROR: No name value pairs specified\n"; printHelp(*STDERR, 1); } my $names = {}; foreach my $namevalue (@namevalues){ my ($name, $value) = &splitnv($namevalue); if ($name){ $names->{$name} = {"value",$value,"replaced",0}; } else { print STDERR "ERROR: Argument not a file and contains no separator: $namevalue\n"; printHelp(*STDERR, 1); } } # Do the modification to each conf file foreach my $file (@files){ # read in the entire file into memory my $contents = ""; open FILE, $file or die $!; while (my $line = <FILE>){ chomp $line; my ($name, $value) = &splitnv($line); # set matching lines to their new value if ($names->{$name}){ $line = $name . $separator . $names->{$name}->{value}; $names->{$name}->{replaced} = 1; } $contents .= "$line\n"; } close FILE or die $!; # add any new lines that didn't already get set foreach my $name (keys %$names){ if (!$names->{$name}->{replaced}){ $contents .= $name . $separator . $names->{$name}->{value}."\n"; } # reset for next file $names->{$name}->{replaced} = 0; } # overwrite the file open FILE, ">$file" or die $!; print FILE $contents; close FILE or die $!; } # Print help message to the specified stream and exit with the specified value sub printHelp(){ my ($stream, $exit) = @_; print $stream "Usage: $scriptname <options> name1=value1 name2=value2 file1.conf file2.conf\n"; print $stream "Options:\n"; print $stream " -s --separator <value> What comes between names and values (default =)\n"; print $stream " -w --whitespace <true|false> Allow space around names and values (default false)\n"; exit $exit; } # Split a string into a name and value using the global separator sub splitnv(){ my ($str) = @_; my $ind = index($str, $separator); return (0,0) if ($ind < 0); my $name = substr($str, 0, $ind); my $value = substr($str, $ind+length($separator)); $name =~ s/(^[ \t])*|([ \t])*$//g if ($whitespace); return ($name, $value); }