Скрипты Bash: обновить файл свойств

propertyOne=1
propertyTwo=a/b
propertyThree=three

Как изменить содержимое файла свойств на следующий шаблон?

  • propertyOne добавит строку перед исходным значением
  • propertyTwo добавит строку в середине
  • propertyThree добавит строку в конце

    propertyOne=apple/1
    propertyTwo=a/and/b
    propertyThree=three/end
    

Я пытался с помощью sed -i -e но я успешен, только если я жестко запрограммирую изменения для каждой строки; какие-либо предложения по улучшению кода?

sed -i -e '/propertyTwo=/ s=.*/=one/2/two' path/to/file

3 ответа

Решение

В этом случае чистое решение Bash предлагает как гибкость, так и надежность (но см. Ниже для более быстрого awk решение).

В то время как решения Bash, которые читают файлы построчно, как правило, медленные, это, вероятно, не будет проблемой для файлов свойств, которые, как правило, небольшие.

#!/usr/bin/env bash

while IFS='=' read -r prop val; do
  case $prop in
    propertyOne)
      val="apple/$val"
      ;;
    propertyTwo)
      val="${val/\///and/}"
      ;;
    propertyThree)
      val="$val/end"
      ;;
  esac
  printf '%s\n' "$prop=$val"
done < file > file.tmp && mv file.tmp file

Встроенный Bash read удобно предлагает логику покоя в строке: указав только 2 переменные в IFS='-' read -r prop value 2-я переменная value получает все после первого =что бы это ни было, даже если оно содержит дополнительные = экземпляров.

< file > file.tmp && mv file.tmp file это общая идиома для (свободно говоря) обновления файла на месте. Технически, измененный контент записывается в темп. файл, и этот темп. Затем файл заменяет оригинал.

Замечания:
* Этот косвенный способ обновления необходим, потому что оболочка не поддерживает чтение и вывод в один и тот же файл одной и той же командой.
* Этот простой подход может быть проблематичным, так как если входной файл был символической ссылкой, он заменяется обычным файлом, права доступа к новому файлу могут быть другими, ...


awk Как показано в ответе Каракфы, это, безусловно, более быстрый выбор, но он идет с оговоркой, которая может быть или не быть проблемой для вас:

Концептуально, файл свойств не строго основан на поле, потому что значение свойства может содержать значение internal = экземпляров.

Если вы разделите ввод на поля =, тогда обработка общего значения может быть проблематичной, потому что у вас не будет единственной переменной, ссылающейся на значение в целом.

Быстрый пример: скажем, у вас есть строка ввода foo=bar=baz , и вы хотите добавить строку @ к существующей стоимости, bar=baz без необходимости заранее знать, было ли существующее значение = символы.
Если вы используете вслепую $2 = $2 "@" для добавления полученное значение будет просто bar@ - другими словами: вы потеряли данные.

Решение этой проблемы требует немного больше работы; вот awk Решение, адаптированное от Каракфы, которое предоставляет все значение в одной переменной val:

awk -F= '
  # Capture the entire value (everything to the right of "=") in variable "val".
  { val= $0; sub("^[^=]+=", "", val) }
  $1 == "propertyOne"   { val = "apple/" val } 
  $1 == "propertyTwo"   { sub(/\//, "/and/", val) }   
  $1 == "propertyThree" { val = val "/end" }
  { print $1 "=" val }  
' file > file.tmp && mv file.tmp file

Примечание: если вы используете GNU awk и номер версии>= 4.1, вы можете использовать -i inplace вместо > file.tmp && mv file.tmp file обновить входной файл на месте (грубо говоря). Помимо того, что более удобный, чем последний подход, -i inplace также сохраняет права исходного файла, но основной подход тот же: файл заменяется, что сопряжено с риском замены символической ссылки обычным файлом.


sed не является хорошим выбором, потому что трудно ограничить замены частью строки в общем виде.

awk в помощь!

$ awk -F'[=/]' '/propertyOne/{$2="apple/"$2} 
                /propertyTwo/{$2=$2"/and/"$3} 
              /propertyThree/{$2=$2"/end"} 
                             {print $1 "=" $2}' file

propertyOne=apple/1
propertyTwo=a/and/b
propertyThree=three/end

определить два разделителя полей, чтобы иметь возможность ссылаться на компоненты. Установите новое второе поле на основе ваших правил и распечатайте.

Примечание Очевидно, что этот подход не будет работать, если у вас есть / или же = как часть вашего контента. Вы можете обобщить первое и последнее правило, чтобы добавить или добавить к значащей части уравнения, но второго варианта нет.

Кроме того, чтобы лучше контролировать правила, которые вы можете изменить /../ в $1=="..." для каждого случая.

Это еще одна альтернатива,

$ awk -F= -v OFS='=' '$1=="propertyOne"{$2="apple/"$2} 
                      $1=="propertyTwo"{sub(/\//,"/and/")}   
                    $1=="propertyThree"{$NF=$NF"/end"}1' file

тестирование с крайними случаями

$ awk -F= ... << EOF
> propertyOne=a==b
> propertyTwo=a==b/c==d
> propertyThree=x/y==z
> Not_propertyOne=no change
> EOF

доходность

propertyOne=apple/a==b
propertyTwo=a==b/and/c==d
propertyThree=x/y==z/end
Not_propertyOne=no change

Вышеуказанные решения являются хорошими. Однако есть еще один способ сделать это на языке Java, который использует тот же класс свойств, который используется в самой Java, и который также можно легко адаптировать для редактирования свойств формата XML.

Давайте воспользуемся новой функцией безымянного класса Java 21 и новой функцией собственного образа GraalVM, чтобы создать команду, которая сделает именно это за нас. Создайте файл с именемSetproperty.java.

Обратите внимание, что здесь нет объявления пакета и объявления класса! Код, показанный ниже, представляет собой полный файл.

      import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import static java.lang.System.out;
import java.util.Date;
import java.util.Properties;

void main(String[] args) throws IOException {
    if(args.length < 2) {
        out.println("Usage: setproperty filename propertyname [value]");
        return;
    }
    final Properties p = new Properties();
    p.load(new FileInputStream(args[0]));
    if(args.length < 3) {
        out.println(p.get(args[1]));
        return;
    }
    p.setProperty(args[1], args[2]);
    p.store(new FileOutputStream(args[0]), "updated " + new Date());
}

Скомпилировать с помощью:

      javac --release 21 --enable-preview Setproperty.java
graalvm-jdk-21.0.1+12.1/bin/native-image --enable-preview -O1 Setproperty
rm Setproperty.class

Теперь вы можете использовать setproperty в своем скрипте:

      ./setproperty foo.properties SomeKey SomeValue

Он работает так же быстро, как и любой другой скомпилированный код — без задержки при запуске JVM. Это отличный пример использования новых функций Java 21 и Native-Image.

Другие вопросы по тегам