POSIXの方法で文字列変数の行数を計算する方法は?

POSIXの方法で文字列変数の行数を計算する方法は?

Bashでこれを行うことができることを知っています。

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

基本的に私が見つけたものはすべて<<<Bash演算子に関連しています。

しかし、POSIXシェルでは<<<定義されておらず、何時間も解決策が見つかりませんでした。私は簡単な解決策があると確信していますが、残念ながらこれまで解決策が見つかりませんでした。

答え1

簡単な答えはwc -l <<< "${string_variable}"ですprintf "%s\n" "${string_variable}" | wc -l

<<<実際、パイプは異なる動作をします。<<<コマンドへの入力として渡すために一時ファイルが生成され、|パイプは生成されます。 bashとpdksh / mksh(ksh93またはzshを除く)では、パイプの右側のコマンドはサブシェルで実行されます。しかし、この特別なケースでは、これらの違いは重要ではありません。

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

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

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

答え2

シェルの組み込み機能に従わず、POSIX互換オプションなどのgrep外部ユーティリティを使用してください。awk

string_variable="one
two
three
four"

grep行の先頭に合わせるには、withを使用してください。

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

そしてawk

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

一部のGNUツール(特にGNU)は、POSIXバージョンのツールを実行するオプションをgrep考慮していません。POSIXLY_CORRECT=1変数の設定によって影響される唯一の動作は、grepコマンドラインフラグが処理される順序です。文書(GNUgrepマニュアル)によると

POSIXLY_CORRECT

設定すると、grep は POSIX の要件に従って動作します。それ以外の場合は、 grep他のGNUプログラムのように動作します。 POSIXでは、ファイル名の後に続くオプションはデフォルトでファイル名として扱われる必要があり、これらのオプションはオペランドリストの前にソートされ、オプションとして扱われます。

バラよりgrepでPOSIXLY_CORRECTを使用するには?

答え3

Here-stringは<<<here-documentのほぼ一行バージョンです<<。前者は標準機能ではなく、後者は標準機能です。<<この場合は。以下は同じでなければなりません。

wc -l <<< "$somevar"

wc -l << EOF
$somevar
EOF

ただし、変数には5行しかありませんが、$somevar両方とも末尾に改行を追加します。6

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行ずつ削除するために、拡張を使用してシェルの行数を完全に計算することもできます...)

答え4

驚くほど頻繁に発生する状況で実際にやるべきことは、すべてを処理することです。空ではない任意の方法で(係数を含む)変数内の行の場合は、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を操作することは、タブで区切られた列形式の入力にスペースを含めることができるパス名を解析するなどの作業にも役立ち、しばしば見落とされる技術です。ただし、通常、space-tab-newlineのIFS設定に含まれる空白文字を意図的に削除すると、一般的に見られると思われる場所で単語の区切りが無効になる可能性があることに注意してください。

たとえば、変数を使用してこのような複雑なコマンドラインを作成する場合は、変数が空でないと設定されている場合にのみ含めることができますffmpeg。通常はを使用してこれを達成できますが、このパラメータ拡張が完了したときにIFSに一般的な空白文字が含まれていない場合、および間のスペースは単語区切り文字として使用されず、すべて単一のパラメータとして渡されるため、理解していませんできません。 。-vf scale=$scalescale${scale:+-vf scale=$scale}-vfscale=ffmpeg-vf scale=$scale

この問題を解決するには、拡張を実行する前にIFSがより正常に設定されていることを確認するか、${scale}2つの拡張を実行する必要があります。${scale:+-vf} ${scale:+scale=$scale}コマンドラインの初期解析中にシェルが実行する単語の分割(実行される単語の分割と比較)そのコマンドライン処理の拡張段階のうち)分割(反対)はI​​FSに依存しません。

この種のタスクを実行したい場合は、時間を費やす価値があるもう1つの方法は、タブと改行のみを含む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引数を作成するsetと、シェルが拡張されます。今後コマンドが実行されるため、組み込み機能自体の実行中にのみ適用される方法で一時的にIFSを設定するには遅すぎます。これが上記のコードスニペットがIFSを別々のステップに設定し、IFSメンテナンスを処理する必要がある理由です。

関連情報