読み込み可能なbashスクリプト


私は、bashパイプラインを書く感じが本当に好きです.一緒にコマンドを文字列には、出力でピーク、私の目標に近いデータをマッサージする別のカップルパイプを追加するのは楽しい.私は、彼らに短いクラスさえ与えました:bgschiller/shell-challenges .
しかし、書くのが楽しいパイプラインが維持するのが苦痛であるとわかるのは簡単です.たとえ1ヶ月後に読んでいるとしても、それぞれの目的が何であるかを思い出すのは難しい.これはパイプラインをより読みやすいスクリプトの中間ファイルに壊すことを学んだトリックです.

仕事
私が取り組んでいる製品は“アラーム”のリストを持っている-エンドユーザーが警告する必要がある条件.また、各サポートされている言語のためのローカライズファイルは、人間の読み取り可能な名前、説明などのアラーム名をマップしています.アラームの多くは、それは難しいものは、すでに翻訳されて追跡するために十分です.これはスクリプトを呼び出します!
アラームファイルは次のようになります.
<!-- Alarms.xml -->
<?xml version="1.0" encoding="utf-8"?>
<config name="AlarmConfig">
   <AlarmList name="IngredientAlarms" version="1.0">
      <Alarm name="MozzarellaTooWarm">
        <!-- some alarm-specific stuff here -->
      </Alarm>
      <Alarm name="PizzaTooCold">
      </Alarm>
      <!-- ... -->
このようなローカライズ
[{ stringId: "alarm_id_MozzarellaTooWarm",
   localString: "MozzarellaTooWarm" },
 { stringId: "alarm_title_MozzarellaTooWarm",
   localString: "Mozzarella Too Warm" },
 { stringId: "alarm_description_MozzarellaTooWarm",
   localString: "The mozzarella has become too warm and must be used within the next five minutes" },
  //...
]

計画を立てる
  • アラームからアラーム名を抽出します.XML使用xpath .
  • 各アラームを事前にする必要がありますalarm_id_ , alarm_title_ , alarm_description_ , alarm_operator_actions_ ローカライズファイルにマッチするために.それをするいくつかの方法を考え出す.
  • 抽出する"stringId" ローカライズファイルの各エントリから.ああ、しかし、ローカライズされる必要があるもののための他のエントリがあり、すべてのアラームではありません.それらをフィルタリングする方法を考え出す.おそらく使用jq そのために.
  • 用途diff キーを実際のキーで比較する.
  • これはbashパイプラインを作る方法のポストではないので、私はその部分の上に光沢があります.ここに私が思い付いたものがあります.
    diff \
      <(join -j 99999 \
        <(echo 'alarm_id_
    alarm_title_
    alarm_description_
    alarm_operator_actions_') \
        <(xpath -q -e config/AlarmList/Alarm/@name Alarms.xml |
          cut -d= -f2 | tr -d '"') \
        | tr -d ' ' | sort) \
      <(jq '.[] | select(.stringId | startswith("alarm_")) |
            .stringId ' i18n/en.json | sort)
    
    それは、そのような方法で畳み込まれて、ちょっと印象的です.しかし、コードを維持して満足しているでしょう.

    一度に一歩
    前のステップは我々が必要としたものを成し遂げました、しかし、それが起こっていたのを見るのはかなり難しかったです.私の意見では
  • 名前は何もない.各プロセス置換は直接親に供給されるので、何もこれまでに名前を受け取りません.我々は、我々の中間体を命名する必要があります!
  • コメントありません.すべての行がバックスラッシュで終わるのでコメントを追加するのは難しいです.bashは“この行を継続”とコメントを追加すると言うことができません.
  • 中間ファイルを使って各部分をいじめるかどうか見てみましょう.
    cat > alarm_attrs << EOF
    alarm_id_
    alarm_title_
    alarm_description_
    alarm_operator_actions_
    EOF
    
    xpath -q -e config/AlarmList/Alarm/@name Alarms.xml |
      cut -d= -f2 | tr -d '"' > expected_alarm_names
    
    join -j 999 alarm_attrs expected_alarm_names |
      tr -d ' ' | sort > expected_localization_keys
    
    jq -r '.[] | select(.stringId | startswith("alarm_")) |
           .stringId' en.json | sort > actual_localization_keys
    
    これはより良いです!各部分を分離して理解することができますし、ステップ間の依存関係を明示的に命名ファイルで追跡されます.すべての左側のコメントといくつかのエラーチェックを追加し、中間ファイルをクリーンアップすることです.
    我々自身の後の浄化は、少しトリッキーです.理想的には、スクリプトが正常に終了したりクラッシュしたり、Ctrl - Cでキャンセルされるかどうかに関係なく、中間結果ファイルを配置しないようにしますtrap これを扱うにはtrap "signal "が発生したときに実行するコマンドを設定します.私たちはすべての中間ファイルをディレクトリと使用に置きますtrap スクリプトを示す信号が火災終了時にそのディレクトリを削除します.
    #!/bin/bash
    
    set -ef -o pipefail
    
    readonly script_name=`basename "$0"`
    usage() {
      cat >&2 << EOF
    Usage: $script_name <path-to-Alarms.xml> <path-to-en.json>
    
      Compare the alarms specified in Alarms.xml against the
      localizations keys provided in a [language-code].json file
      (eg, en.json). Warn if any keys are missing or unexpected.
    EOF
      exit 2
    }
    
    if [[ $# != 2 ]]; then
       echo "error: expected 2 arguments, received $#" 1>&2
       usage
    fi
    
    readonly ALARM_CONFIG_XML=$1
    if [[ ${ALARM_CONFIG_XML: -4} != ".xml" || ! -e $ALARM_CONFIG_XML ]]; then
       echo "error: unable to find XML file at $ALARM_CONFIG_XML" 1>&2
       usage
    fi
    readonly LOCALIZATION_JSON=$2
    if [[ ${LOCALIZATION_JSON: -5} != ".json" || ! -e $LOCALIZATION_JSON ]]; then
       echo "error: unable to find JSON file at $LOCALIZATION_JSON" 1>&2
       usage
    fi
    
    # Make a directory for intermediate results
    tmpdir=$(mktemp -d -p .)
    # ensure it's removed when this script exits
    trap "rm -rf $tmpdir" EXIT HUP INT TERM
    # note: when debugging, turn off that `trap` line to keep
    # intermediate results around
    
    # The plan is ultimately to use diff to compare names between
    # Alarms.xml and en.json. diff will tell us if any names
    # appear in one file but are missing in the other (checks
    # both directions for a mismatch).In pursuit of this, we need
    # to create a couple of temporary files:
    #  1) all the alarm names we expect to find (based on Alarms.xml)
    #  2) all the names actually present in the localization file.
    # A complication: the location file uses a flat format to
    # store the id, title, description, and operator_actions:
    #   { "stringId": "alarm_id_MozzarellaTooWarm", ... },
    #   { "stringId": "alarm_title_MozzarellaTooWarm", ... },
    #   { "stringId": "alarm_description_MozzarellaTooWarm", ... },
    #   { "stringId": "alarm_operator_actions_MozzarellaTooWarm", ... },
    # We want to check that *all* of these keys are present, so
    # we use a cross product of (alarm names) X (those attributes)
    
    cat > $tmpdir/alarm_attrs << EOF
    alarm_id_
    alarm_title_
    alarm_description_
    alarm_operator_actions_
    EOF
    
    xpath -q -e config/AlarmList/Alarm/@name $ALARM_CONFIG_XML |
     cut -d= -f2 | tr -d '"' > $tmpdir/expected_alarm_names
    
    # trick to compute cross product: use `join` with a join
    # field that doesn't exist (999). since both files lack a
    # field at 999, they will compare equal for every key, and
    # each line of the left file will be joined with each line
    # of the right file--a cross product.
    join -j 999 $tmpdir/alarm_attrs $tmpdir/expected_alarm_names |
    tr -d ' ' | sort > $tmpdir/expected_localization_keys
    
    jq -r '.[] | select(.stringId | startswith("alarm_")) |
           .stringId' $LOCALIZATION_JSON |
      sort > $tmpdir/actual_localization_keys
    
    if diff $tmpdir/expected_localization_keys $tmpdir/actual_localization_keys; then
       echo "Success! Found all" $(wc -l $tmpdir/expected_localization_keys) "expected localization keys"  1>&2
    else
       echo "Failure. Found discrepancies between expected alarm names and actual localization keys" 1>&2
       exit 1
    fi