文字列変数の行数をPOSIXで数える方法は?


10

私はこれをバッシュでできることを知っています:

wc -l <<< "${string_variable}"

基本的に、私が見つけたものはすべて<<<Bashオペレーターに関係していました。

しかし、POSIXシェルで<<<はは定義されておらず、何時間も別のアプローチを見つけることができませんでした。これには簡単な解決策があると確信していますが、残念ながら今のところ見つかりませんでした。

回答:


11

簡単な答えは、これwc -l <<< "${string_variable}"はのksh / bash / zshショートカットですprintf "%s\n" "${string_variable}" | wc -l

方法の違い、実際に存在する<<<と配管作業は:<<<一方、コマンドへの入力として渡される一時ファイルを作成し|、パイプを作成します。bashおよびpdksh / mksh(ksh93またはzshは除く)では、パイプの右側のコマンドはサブシェルで実行されます。しかし、この特定のケースでは、これらの違いは重要ではありません。

行のカウントに関しては、これは変数が空ではなく、改行で終わっていないことを前提としています。変数がコマンド置換の結果である場合は、改行で終わっていないため、ほとんどの場合は正しい結果が得られますが、空の文字列に対しては1が得られます。

とには2つの違いがvar=$(somecommand); wc -l <<<"$var"ありsomecommand | wc -lます。コマンド置換と一時変数を使用すると、末尾の空白行が削除され、出力の最後の行が改行で終了したかどうかが忘れられます(コマンドが空でない有効なテキストファイルを出力する場合は常にそうなります) 、および出力が空の場合は1だけオーバーカウントします。結果とカウント行の両方を保持したい場合は、既知のテキストを追加し、最後にそれを削除することで実行できます。

output=$(somecommand; echo .)
line_count=$(($(printf "%s\n" "$output" | wc -l) - 1))
printf "The exact output is:\n%s" "${output%.}"

1
@Inian Keeping wc -lは元のキーピングとまったく同じです。<<<$foo値が$foo$foo空の場合でも)に改行を追加します。私の答えで、これが望まれていたことではなかった理由を説明しますが、それは求められたものです。
Gilles「SO-邪悪なことをやめよう」

2

以下のような外部のユーティリティを使用して、ビルトインをシェルに適合しないgrepawkPOSIX準拠したオプションで、

string_variable="one
two
three
four"

行頭とgrep一致するようにする

printf '%s' "${string_variable}" | grep -c '^'
4

そして awk

printf '%s' "${string_variable}" | awk 'BEGIN { count=0 } NF { count++ } END { print count }'

一部のGNUツール、特にGNU grepPOSIXLY_CORRECT=1、ツールのPOSIXバージョンを実行するオプションを尊重しないことに注意してください。grep変数を設定することによって影響を受けるのみ動作コマンドラインフラグの順序の処理の違いであろう。ドキュメント(GNU grepマニュアル)から、

POSIXLY_CORRECT

設定すると、grepはPOSIXの要求どおりに動作します。それ以外の場合は、grep他のGNUプログラムのように 動作します。POSIXでは、ファイル名に続くオプションをファイル名として扱う必要があります。デフォルトでは、このようなオプションはオペランドリストの前に並べ替えられ、オプションとして扱われます。

grepでPOSIXLY_CORRECTを使用する方法を参照してください


2
確かにwc -lここでまだ実行可能ですか?
Michael Homer

@MichaelHomer:私が観察したことからwc -l、適切に改行で区切られたストリームが必要です(正しくカウントするために末尾に「\ n」が付いています)。で使用する単純なFIFOを使用することはできませんprintf。たとえばprintf '%s' "${string_variable}" | wc -l、期待どおりに機能しない可能性がありますが、ヘレストリングによって追加された<<<トレーリングのために\nそうなります
Inian

1
printf '%s\n'あなたがそれを取り出す前に、それが何をしていたのか...
マイケルホーマー

1

here-string <<<は、ほぼhere-documentの1行バージョンです<<。前者は標準機能ではありませんが、後者は標準機能です。<<この場合も使用できます。これらは同等でなければなりません:

wc -l <<< "$somevar"

wc -l << EOF
$somevar
EOF

両方の末尾に余分な改行を追加することをんけれども$somevar、この版画例えば、6:変数は唯一の5つのラインを持っているにもかかわらず、

s=$'foo\n\n\nbar\n\n'
wc -l <<< "$s"

を使用printfすると、追加の改行が必要かどうかを決定できます。

printf "%s\n" "$s" | wc -l         # 6
printf "%s"   "$s" | wc -l         # 5

ただし、wc完全な行(または文字列内の改行文字の数)のみをカウントすることに注意してください。grep -c ^最後の行の断片も数える必要があります。

s='foo'
printf "%s" "$s" | wc -l           # 0 !

printf "%s" "$s" | grep -c ^       # 1

(もちろん${var%...}、ループを使用して一度に1つずつ削除する展開を使用して、シェル内の行全体を数えることもできます...)


0

変数内の空でないすべての行を何らかの方法で(それらを数えることを含めて)処理するという驚くほど頻繁なケースでは、IFSを改行だけに設定し、シェルの単語分割メカニズムを使用して中断することができます。空ではない行が離れています。

たとえば、次の小さなシェル関数は、指定されたすべての引数内の空でない行を合計します。

lines() (
IFS='
'
set -f #disable pathname expansion
set -- $*
echo $#
)

ここでは、中括弧ではなく括弧を使用して、関数本体の複合コマンドを形成しています。これにより、関数がサブシェルで実行されるため、すべての呼び出しで外部のIFS変数とパス名展開の設定が汚染されません。

空でない行を繰り返し処理したい場合は、同様に行うことができます。

IFS='
'
set -f
for line in $lines
do
    printf '[%s]\n' $line
done

この方法でIFSを操作することは、見過ごされがちなテクニックであり、タブ区切りの列入力からのスペースを含む可能性のあるパス名の解析などにも役立ちます。ただし、IFSのデフォルト設定であるspace-tab-newlineに通常含まれているスペース文字を意図的に削除すると、通常は表示されるはずの場所で単語分割が無効になる可能性があることに注意する必要があります。

たとえば、変数を使用してなどの複雑なコマンドラインを作成する場合、変数が空でないものに設定されている場合ffmpeg-vf scale=$scaleのみ含めることができscaleます。通常、あなたがこれを達成できる${scale:+-vf scale=$scale}IFSは、このパラメータ展開が行われている時にその通常の空白文字が含まれていない場合は、間にスペース-vfscale=単語の区切りとして使用されることはありませんとffmpegのすべてを渡される-vf scale=$scale1つの引数として、それは理解できません。

これを修正するには、${scale}拡張を行う前にIFSがより正常に設定されていることを確認するか、2つの拡張を行う必要があります${scale:+-vf} ${scale:+scale=$scale}。コマンドラインの初期解析中にシェルが行う単語分割は、コマンドライン処理の拡張フェーズ中に行う分割とは異なり、IFSに依存しません。

あなたがこの種のことをするつもりならあなたの価値がある他の何かがタブと改行だけを保持するために2つのグローバルシェル変数を作成するでしょう:

t=' '
n='
'

そうすればあなただけ含めることができます$tし、$nあなたが引用された空白を持つすべてのコードタブ、改行ではなく、ポイ捨てを必要とする拡張インチ 他のメカニズムがないPOSIXシェルで引用符で囲まれた空白を完全に避けたい場合はprintf、コマンド展開の末尾の改行の削除を回避するために少し手を加える必要があります。

nt=$(printf '\n\t')
n=${nt%?}
t=${nt#?}

コマンドごとの環境変数であるかのようにIFSを設定するとうまくいく場合があります。たとえば、次のループは、タブ区切りの入力ファイルの各行からスペースとスケーリング係数を含めることができるパス名を読み取るループです。

while IFS=$t read -r path scale
do
    ffmpeg -i "$path" ${scale:+-vf scale=$scale} "${path%.*}.out.mkv"
done <recode-queue.txt

この場合、read組み込み関数はIFSが単なるタブに設定されていることを認識しているため、スペースで読み取った入力行も分割しません。しかし、IFS=$t set -- $lines 機能しません。シェルは、コマンドを実行する前に組み込みの引数を$lines構築するときに拡張するため、組み込み自体の実行中にのみ適用されるIFSの一時的な設定が遅すぎます。これが、上記で提供したコードスニペットがすべてIFSを別のステップで設定する理由であり、IFSがそれを保持する問題に対処する必要があるのはこのためです。set

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.