シェルコマンドの出力の文字数


12

コマンドの出力の文字数を1ステップで計算する必要があるスクリプトを書いています。

たとえば、コマンドの出力は10文字なので、コマンドを使用するreadlink -f /etc/fstabと戻り10ます。

これは、次のコードを使用して、格納された変数ですでに可能です。

variable="somestring";
echo ${#variable};
# 10

残念ながら、コマンドで生成された文字列で同じ数式を使用しても機能しません。

${#(readlink -f /etc/fstab)};
# bash: ${#(readlink -f /etc/fstab)}: bad substitution

私は最初に出力を変数に保存することでこれを行うことが可能であることを理解しています:

variable=$(readlink -f /etc/fstab);
echo ${#variable};

しかし、私は余分なステップを削除したいと思います。

これは可能ですか?組み込みまたは標準のユーティリティのみを使用したAlmquistシェル(sh)との互換性が推奨されます。


1
の出力readlink -f /etc/fstab11文字です。改行を忘れないでください。それ以外の場合は/etc/fstabluser@cern:~$ 、シェルから実行したときに表示されます。
Phil Frost

@PhilFrost面白いプロンプトがあるようですが、CERNで働いていますか?
Dmitry Grigoryev 2016

回答:


9

GNU exprを

$ expr length + "$(readlink -f /etc/fstab)"
10

+GNUの特別な機能があるexprことを確認、次の引数が、あることを起こる場合でも、文字列として扱われるようにするexprなどのオペレータはmatchlength+...

上記は、出力の後続の改行を削除します。それを回避するには:

$ expr length + "$(readlink -f /etc/fstab; printf .)" - 2
10

最後の改行と追加した文字のため、結果は2に差し引かれました。readlink.

Unicode文字列でexprは、文字数ではなくバイト単位で文字列の長さを返すため、動作しないようです(行654を参照)

$ LC_ALL=C.UTF-8 expr length ăaa
4

だから、あなたは使うことができます:

$ printf "ăaa" | LC_ALL=C.UTF-8 wc -m
3

POSIXLY:

$ expr " $(readlink -f /etc/fstab; printf .)" : ".*" - 3
10

コマンド置換の前のスペースは、コマンドがで始まる文字列でクラッシュし-ないようにするため、3を減算する必要があります。


ありがとう!3番目の例はがなくても機能するようです。これLC_ALL=C.UTF-8により、文字列のエンコーディングが事前にわからない場合に、処理が大幅に簡略化されます。
user339676 2014年

2
expr length $(echo "*")—いいえ。少なくとも二重引用符を使用してください:expr length "$(…)"。しかし、これはコマンドから後続の改行を取り除きます。これは、コマンド置換の避けられない機能です。(あなたはそれを回避することができますが、答えはさらに複雑になります。)
Gilles「SO-邪悪なことをやめなさい」

6

シェルのビルトインでこれを行う方法はわかりませんが(Gnoucはそうです)、標準のツールが役立ちます:

  1. wc -m文字数を数えるのに使えます。残念ながら、これは最後の改行もカウントするため、最初にそれを取り除く必要があります。

    readlink -f /etc/fstab | tr -d '\n' | wc -m
  2. もちろん使用できます awk

    readlink -f /etc/fstab | awk '{print length($0)}'
  3. またはPerl

    readlink -f /etc/fstab | perl -lne 'print length'

expr内蔵されているということですか?どのシェルに?
mikeserv 2014年

5

私は通常、次のようにします。

$ echo -n "$variable" | wc -m
10

コマンドを実行するには、次のように適合させます。

$ echo -n "$(readlink -f /etc/fstab)" | wc -m
10

このアプローチは、2つのステップで行っていたことに似ていますが、それらを1つのライナーに結合している点が異なります。


2
-m代わりに使用する必要があります-c。ユニコード文字を使用すると、アプローチが壊れます。
cuonglm 2014年

1
なぜ単純ではないのですreadlink -f /etc/fstab | wc -mか?
Phil Frost

1
なぜこの信頼性の低い方法の代わりに使用するの${#variable}ですか?少なくとも、二重引用符を使用するecho -n "$variable"が、これはまだの例であれば値が失敗しvariableているが-e。コマンド置換と組み合わせて使用​​する場合は、後続の改行が削除されることに注意してください。
Gilles「SO-邪悪なことをやめなさい」2014

@philfrost b / c私が示したものは、オペレーションがすでに考えていたことに基づいています。また、varsで事前にセットアップしていて、後の長さを必要とするすべてのcmdでも機能します。また、terdonはすでにその例を持っています。
slm

1

外部ユーティリティを呼び出すこともできますが(他の回答を参照)、スクリプトが遅くなり、適切な配管を行うことが困難になります。

Zsh

zshでは${#$(readlink -f /etc/fstab)}、コマンド置換の長さを取得するように書き込むことができます。これはコマンド出力の長さではなく、末尾の改行なしの出力の長さであることに注意してください。

出力の正確な長さが必要な場合は、最後に余分な非改行文字を出力し、1つ減算します。

$((${#$(readlink -f /etc/fstab; echo .)} - 1))

コマンドの出力のペイロードが必要な場合は、ここで2を減算する必要がありreadlink -fます。これは、の出力が正規パスと改行であるためです。

$((${#$(readlink -f /etc/fstab; echo .)} - 2))

これは${#$(readlink -f /etc/fstab)}、正規のパス自体が改行で終わるというまれなケースとは異なります。

この特定の例では、外部ユーティリティはまったく必要ありません。zshにはreadlink -f、履歴修飾子を介したと同等の組み込みの構成要素があるためAです。

echo /etc/fstab(:A)

長さを取得するには、パラメーター展開で履歴修飾子を使用します。

${#${:-/etc/fstab}:A}

変数にファイル名がある場合filename、それはそうなります${#filename:A}

Bourne / POSIXスタイルのシェル

純粋なBourne / POSIXシェル(Bourne、ash、mksh、ksh93、bash、yash…)には、私が知っている同様の拡張機能はありません。コマンド置換の出力またはパラメーター置換のネストにパラメーター置換を適用する必要がある場合は、連続するステージを使用します。

必要に応じて、処理を関数に組み込むことができます。

command_output_length_sans_trailing_newlines () {
  set -- "$("$@")"
  echo "${#1}"
}

または

command_output_length () {
  set -- "$("$@"; echo .)"
  echo "$((${#1} - 1))"
}

しかし、通常はメリットはありません。ksh93を除き、追加のフォークが関数の出力を使用できるようにするため、スクリプトが遅くなり、読みやすさの利点はほとんどありません。

ここでも、の出力readlink -fは正規パスと改行です。正規パスの長さが必要な場合は、で1ではなく2を引きcommand_output_lengthます。使用するには、command_output_length_sans_trailing_newlines正規のパス自体が改行で終わっていない場合にのみ、正しい結果を与えます。

バイトと文字

${#…}バイト単位ではなく、文字単位の長さが想定されているため、マルチバイトロケールでは違いがあります。ksh93、bash、およびzshの合理的に最新のバージョンはLC_CTYPE${#…}構成が展開されたときのの値に従って文字数で長さを計算します。他の多くの一般的なシェルは、実際にはマルチバイトロケールをサポートしていません。ダッシュ0.5.7以降、mksh 46およびposh 0.12.3では${#…}、長さをバイト単位で返します。信頼できる方法で文字の長さを知りたい場合は、wcユーティリティを使用してください:

$(readlink -f /etc/fstab | wc -m)

$LC_CTYPE有効なロケールを指定している限り、これがエラーになるか(マルチバイトロケールをサポートしていない古いプラットフォームまたは制限されたプラットフォームで)、正しい長さの文字を返すと確信できます。(Unicodeの場合、「文字の長さ」はコードポイントの数を意味します。文字の結合などの複雑さのため、グリフの数はさらに別の話です。)

長さをバイト単位で取得したい場合は、LC_CTYPE=C一時的に設定するか、のwc -c代わりに使用してくださいwc -m

でバイトまたは文字をカウントwcすると、コマンドの後続の改行が含まれます。正規パスの長さをバイト単位で知りたい場合は、

$(($(readlink -f /etc/fstab | wc -c) - 1))

文字で取得するには、2を引きます。


@cuonglmいいえ、1を引く必要がありecho .ます。2つの文字を追加しますが、2番目の文字はコマンド置換によって削除される末尾の改行です。
Gilles「SO-邪悪なことをやめなさい」

改行はreadlink出力に加えて.by echoです。echo .2つの文字を追加することに同意しますが、末尾の改行は削除されます。試してみるprintf .か、私の回答unix.stackexchange.com/a/160499/38906を参照してください。
cuonglm

@cuonglm質問は、コマンドの出力の文字数を尋ねました。の出力readlinkは、リンクターゲットと改行です。
Gilles「SO-邪悪なことをやめよう」

0

これは機能dashしますが、ターゲットの変数が確実に空または未設定である必要があります。これが実際に2つのコマンドである理由です- $l最初は明示的に空にします。

l=;printf '%.slen is %d and result is %s\n' \
    "${l:=$(readlink -f /etc/fstab)}" "${#l}" "$l"

出力

len is 10 and result is /etc/fstab

これはすべてのシェルビルトインreadlinkです(もちろん、含まれていません)。ただし、現在のシェルで評価する場合は、lenを取得する前に割り当てを行う必要があることを意味します。そのため%.sprintfフォーマット文字列の最初の引数を無効にして、もう一度追加します。printfの引数リストの末尾にあるリテラル値。

eval

l=$(readlink -f /etc/fstab) eval 'l=${#l}:$l'
printf %s\\n "$l"

出力

10:/etc/fstab

同じことに近づくことができますが、最初のコマンドの変数の出力の代わりに、それをstdoutに取得します。

PS4='${#0}:$0' dash -cx '2>&1' "$(readlink -f /etc/fstab)"

...書き込みます...

10:/etc/fstab

...現在のシェルの変数に値を割り当てずにファイル記述子1に。


1
それはまさにOPが避けたかったことではありませんか?「私は最初に出力を変数に保存することでこれを行うことが可能であることを理解しています:variable=$(readlink -f /etc/fstab); echo ${#variable};しかし、余分なステップを削除したいと思います。」
terdon

@terdon、おそらく私は誤解していましたが、セミコロンが問題であり変数ではないというのが私の印象でした。そのため、これらはシェルビルトインのみを使用して単一の簡単なコマンドでlenと出力を取得します。シェルはないのexec READLINKを行い、その後のexec expr例えば、。それはおそらく唯一何とか私は困難それがかもしれ理由を理解したんだけど、私はそれが重要だった場合がある可能性が疑わ認めるLEN吸蔵値を、取得した場合に問題になります。
mikeserv 2014年

1
eval道は、道で、ここではおそらく最もクリーンである- -それは、単一の実行中に同じVAR名への出力とlenを割り当て非常に近いことにl=length(l):out(l)。やってexpr length $(command) ない方法により、LENの賛成で値を塞ぎます。
mikeserv 2014年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.