私はこれをバッシュでできることを知っています:
wc -l <<< "${string_variable}"
基本的に、私が見つけたすべてが関係している<<<
バッシュ演算子。
しかし、POSIXシェルでは、<<<
は未定義であり、何時間も別のアプローチを見つけることができませんでした。これには簡単な解決策があると確信していますが、残念ながら今のところ見つかりませんでした。
簡単な答えは、_wc -l <<< "${string_variable}"
_は_printf "%s\n" "${string_variable}" | wc -l
_のksh/bash/zshショートカットであるということです。
_<<<
_とパイプの動作には実際の違いがあります。_<<<
_は、コマンドへの入力として渡される一時ファイルを作成しますが、_|
_はパイプを作成します。 bashおよびpdksh/mksh(ksh93またはzshは除く)では、パイプの右側にあるコマンドはサブシェルで実行されます。しかし、この特定のケースでは、これらの違いは重要ではありません。
行のカウントに関しては、これは変数が空ではなく、改行で終わっていないことを前提としています。変数がコマンド置換の結果である場合は、改行で終わっていないため、ほとんどの場合は正しい結果が得られますが、空の文字列に対しては1が得られます。
var=$(somecommand); wc -l <<<"$var"
と_somecommand | wc -l
_の間には2つの違いがあります。コマンド置換と一時変数を使用すると、末尾の空白行が削除され、出力の最後の行が改行で終わったかどうかを忘れます(それはコマンドが空でない有効なテキストファイルを出力する場合は常に実行し、出力が空の場合は1を超過します。結果とカウント行の両方を保持したい場合は、既知のテキストを追加し、最後にそれを削除することで実行できます。
_output=$(somecommand; echo .)
line_count=$(($(printf "%s\n" "$output" | wc -l) - 1))
printf "The exact output is:\n%s" "${output%.}"
_
Shellビルトインに準拠せず、POSIX準拠のオプションでgrep
やawk
などの外部ユーティリティを使用します。
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 grep
はPOSIXLY_CORRECT=1
オプションを考慮せず、POSIXバージョンのツール。grep
では、変数の設定によって影響を受ける唯一の動作は、コマンドラインフラグの順序の処理の違いになります。ドキュメント(GNU grep
マニュアル)から、
POSIXLY_CORRECT
設定されている場合、grepはPOSIXが要求するとおりに動作します。それ以外の場合、
grep
は他のGNUプログラムのように動作します。POSIXでは、ファイル名に続くオプションをファイル名として扱う必要があります。デフォルトでは、このようなオプションはオペランドリストであり、オプションとして扱われます。
Here-string <<<
は、ほぼhere-document <<
の1行のバージョンです。前者は標準機能ではありませんが、後者は標準機能です。この場合、<<
も使用できます。これらは同等でなければなりません:
wc -l <<< "$somevar"
wc -l << EOF
$somevar
EOF
ただし、どちらも$somevar
の最後に余分な改行を追加することに注意してください。変数に5行しかない場合でも、これは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つずつ削除することにより、シェルで行全体を数えることもできます...)
あなたが実際に行う必要があるのは、すべてを処理するという驚くほど頻繁なケースです 空でない 変数内の行をいくつかの方法で(それらをカウントすることを含めて)変更するには、IFSを改行だけに設定し、シェルのWord分割メカニズムを使用して空でない行を分割します。
たとえば、次の小さなシェル関数は、指定されたすべての引数内の空でない行を合計します。
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に通常含まれているスペース文字を意図的に削除すると、通常は表示されるはずの場所でWordの分割が無効になる可能性があることに注意する必要があります。
たとえば、変数を使用してffmpeg
などの複雑なコマンドラインを作成する場合、変数scale
が非値に設定されている場合にのみ-vf scale=$scale
を含めることができます。空の。通常、これは${scale:+-vf scale=$scale}
で実現できますが、このパラメーターの展開が行われるときにIFSに通常のスペース文字が含まれていない場合、-vf
とscale=
の間のスペースは使用されません。 Wordセパレータとして、そしてffmpeg
は-vf scale=$scale
のすべてが単一の引数として渡されますが、理解できません。
これを修正するには、${scale}
拡張を実行する前にIFSがより正常に設定されていることを確認するか、2つの拡張を実行する必要があります:${scale:+-vf} ${scale:+scale=$scale}
。コマンドラインの初期解析中にシェルが行うWordの分割は、コマンドラインを処理する拡張フェーズで行う分割とは異なり、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
しない 動作:シェルはset
ビルトインの引数を構築するときに$lines
を展開します 前 コマンドを実行するので、組み込み自体の実行中にのみ適用される方法でのIFSの一時的な設定は遅すぎます。これが、上記で提供したコードスニペットがすべてIFSを別のステップで設定する理由であり、IFSがそれを保持する問題に対処する必要があるのはこのためです。