コマンドラインで構成ファイルを変更するときに、構成ファイルで設定を見つけてその設定がある場合は、その行を変更したいことがよくあります。設定が存在しない場合は、ファイルの末尾に追加したいと思います。
私は結局次のことをしました。
if [ `grep -c '^setting=' example.conf` == 0 ]
then
echo "setting=value" >> example.conf
else
sed -i 's/^setting=.*/setting=value/g' example.conf
fi
このように簡単なことをするにはあまりにも多くのコードが必要なようです。これは、設定ファイルを追加する前にすでに新しい行で終わっていることを確認するなど、基本的な操作も行いません。確かにこれを行うことができるユーティリティがあるか、より簡単なコマンドを使用できます。
答え1
これは私が作成したばかりのconfset Perlスクリプトです。これをパスに配置します。
- 1回の呼び出しで複数のファイルを処理できます
- 1回の呼び出しで各ファイルの複数の設定値を変更できます。
- 区切り文字を指定できます(使用
--separator
)。 - 名前の周囲のスペースのオプションを開いたままにします。
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)
したがって、質問で説明されている状況を処理するために、次のように呼び出します。
confset example.conf setting=value
スクリプトは次のとおりです。
#!/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);
}
答え2
これを処理するために追加のロジックを使用できますawk
。
BEGIN { FS = OFS = "=" }
$1 == "setting" { $2 = "value"; found=1 }
{print}
END { if (!found) { print "setting=value" }
最後に属性が見つからない場合、found
属性は設定されず、END句に新しい構成行が追加されます。FS=OFS=
フォーマットが同じであることを確認してください。印刷時には、最後の行を含む常に改行(ORS)が送信されます。空行とコメントはそのまま渡されます。