web-dev-qa-db-ja.com

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

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

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

基本的に、私が見つけたすべてが関係している<<<バッシュ演算子。

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

10

簡単な答えは、_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準拠のオプションでgrepawkなどの外部ユーティリティを使用します。

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
Inian

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つずつ削除することにより、シェルで行全体を数えることもできます...)

1
ilkkachu

あなたが実際に行う必要があるのは、すべてを処理するという驚くほど頻繁なケースです 空でない 変数内の行をいくつかの方法で(それらをカウントすることを含めて)変更するには、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に通常のスペース文字が含まれていない場合、-vfscale=の間のスペースは使用されません。 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がそれを保持する問題に対処する必要があるのはこのためです。

0
flabdablet