追いかけることについてx。

追いかけることについてx。

コマンド置換の正確な出力をキャプチャできるようにしたいです。末尾の改行を含む

デフォルトでは削除されているので、これを保存するには少しの操作が必要になることがあります。元の終了コードを維持したいと思います。

たとえば、さまざまな数の末尾の改行と終了コードを含むコマンドがあります。

f(){ for i in $(seq "$((RANDOM % 3))"); do echo; done; return $((RANDOM % 256));}
export -f f

私は次のようなものを実行したいと思います:

exact_output f

出力は次のとおりです。

Output: $'\n\n'
Exit: 5

bash私はPOSIXとPOSIXの両方に興味がありますsh

ベストアンサー1

POSIXシェル

通常(1 2 サム 4 5 6 7 8 9 10 11 12 13 14 15 )コマンドの完全な標準出力を得るための秘密は次のとおりです。

output=$(cmd; ret=$?; echo .; exit "$ret")
ret=$?
output=${output%.}

アイデアは、.\n削除する追加の.command置換を追加することです。それ \n。その後、.シェルを使用してください${output%.}

Python以外のシェルでは、zsh出力にNULバイトがある場合、この方法はまだ機能しません。の場合、yash出力がテキストでない限り、この方法は機能しません。

また、一部のロケールでは、最後にどの文字が挿入されるかが重要です。.一般的には大丈夫ですが(下記参照)、一部はそうではないかもしれません。たとえば、x(他の回答で使用されます)、または@BIG5、GB18030、またはBIG5HKSCS文字セットを使用するロケールでは機能しません。これらの文字セットには多くの文字エンコーディングが使用されます。終わるxまたは (0x78, 0x40) のエンコードと@同じバイト

たとえば、ūBIG5HKSCSでは0x88 0x78です(xASCIIの0x78に似ており、システム内のすべての文字セットは英語のアルファベットやを含む@移植可能な文字セット内のすべての文字に対して同じエンコードを持つ必要があります.)。したがって、cmdそれprintf '\x88'自体がそのエンコーディングで有効な文字ではなく単にバイトシーケンスである場合、xその後に挿入すると、実際に埋め込まれたもの(バイトを構成する2バイト)で${output%x}それを削除する方法はありません。シーケンスはこのエンコーディングで有効な文字です。)x$outputū

使用する必要が.ある/全体的に良い、POSIXの要件に従って:

  • <period>" 、<slash><newline>関連するエンコーディング値は、<carriage-return>実装でサポートされているすべてのロケールで変更されずに保持されます。これは、これらの値がすべてのロケール/エンコードで同じバイナリ表現を持つことを意味します。
  • 「同様に、、、<period>および<slash>をエンコードするために使用される<newline>バイト値は、<carriage-return>すべてのロケールで他の文字の一部として発生してはなりません。」これは、これらのバイト/文字が有効な文字の部分バイトシーケンスを完了できないため、上記の状況が発生しないことを意味します。ロケール/エンコーディングから。 (望むより6.1 移植可能な文字セット)

上記はPortable Character Setの他のキャラクターには適用されません。

次のような別のアプローチ@Isaacが議論しました、ロケールを次に変更しますC(これもランダムなシングルバイト正しく削除)、最後の文字(${output%.})のみを削除します。通常、これを使用する必要がありますLC_ALL(原則としてはLC_CTYPE十分ですが、すでに設定されている項目によって誤って無視される可能性がありますLC_ALL)。また、元の値を復元する必要があります(たとえば、localePOSIXと互換性のない値が関数に使用されます)。ただし、一部のシェルはPOSIX要件にもかかわらず、実行時のロケール変更をサポートしていません。

.またはを使用すると、/これらすべてを回避できます。

bash/zshの代替

出力に NUL がないとし、bashand を使用すると、次のこともできます。zsh

IFS= read -rd '' output < <(cmd)

終了ステータスを取得するには、cmd一部のバージョンでは実行できますが、実行できませんが、で作成して終了ステータスを取得できます。wait "$!"; ret=$?bashzshzshcmd | IFS= read -rd '' output$pipestatus[1]

rc/es/赤永

rc完全性のために//演算子esがあることに注意してください。akangaここで`cmd(または`{cmd}より複雑なコマンドの場合)で表されるコマンド置換は、リスト(デフォルトでは分割$ifs、スペースタブ改行)を返します。 Bourne のようなシェルとは異なり、これらのシェルでは改行の削除は$ifs分割の一部としてのみ行われます。したがって、指定された区切り記号を持つフォームを空にしたり$ifs使用したりできます。``(seps){cmd}

ifs = ''; output = `cmd

または:

output = ``()cmd

それにもかかわらず、コマンドの終了状態は失われます。これを出力に含めてから抽出する必要があります。

魚では、コマンド置換はサブシェルを使用し、(cmd)サブシェルは含まれません。

set var (cmd)

$varif 出力のすべての行を含む配列を作成するか、最も多くの行を削除します。cmd$IFScmd一つ(大みんな他のほとんどのシェルから)$IFS空の場合は改行です。

したがって、これは空の場合でも依然として問題になります(printf 'a\nb')(printf 'a\nb\n')$IFS

この問題を解決するために私が考えることができる最良の方法は次のとおりです。

function exact_output
  set -l IFS . # non-empty IFS
  set -l ret
  set -l lines (
    cmd
    set ret $status
    echo
  )
  set -g output ''
  set -l line
  test (count $lines) -le 1; or for line in $lines[1..-2]
    set output $output$line\n
  end
  set output $output$lines[-1]
  return $ret
end

バージョン 3.4.0 (2022 年 3 月リリース) 以降では、次のことができます。

set output (cmd | string collect --allow-empty --no-trim-newlines)

以前のバージョンでは、次のことができます。

read -z output < (begin; cmd; set ret $status; end | psub)

出力がない場合、$outputこれは空の要素を持つリストではなく空のリストです。

バージョン 3.4.0 では$(...)(...)二重引用符で使用できる点を除いて、その動作のサポートも追加されました。この場合、POSIXシェルのように動作します。出力は1行に分割されませんが、すべての末尾の改行は削除されます。

ボンシェル

Bourneシェルはフォーム$(...)${var%pattern}演算子をサポートしていないため、実装するのは難しいです。 1つの方法は、評価と参照を使用することです。

eval "
  output='`
    exec 4>&1
    ret=\`
      exec 3>&1 >&4 4>&-
      (cmd 3>&-; echo \"\$?\" >&3; printf \"'\") |
        awk 3>&- -v RS=\\\\' -v ORS= -v b='\\\\\\\\' '
          NR > 1 {print RS b RS RS}; {print}; END {print RS}'
    \`
    echo \";ret=\$ret\"
  `"

ここでは

output='output of cmd
with the single quotes escaped as '\''
';ret=X

evalPOSIXアプローチの場合、他の文字の終わりでエンコーディングを見つけることができる文字の1つは'問題が発生します(コマンド注入の脆弱性になるため、より悪い問題).使用される技術(これには問題があるため使用しないでください(特定の文字にバックスラッシュが必要な場合も含まれませ\ん)。"..."ここでは')の後にのみ使用してください。

tcsh

バラよりtcshはコマンド置換 `...`で改行を維持します。

(終了状態は気にしないでください。一時ファイルに保存すると問題を解決できます(echo $status > $tempfile:qコマンドの後))

おすすめ記事