web-dev-qa-db-ja.com

文字数が最も少ない行を見つける方法

一般的なUNIXコマンドを使用して、シェルスクリプトを作成しています。文字数が最も少ない行(空白を含む)を取得する必要があります。最大20行まで表示できます。

head -$L | tail -1 | wc -mを使用して行Lの文字数を見つけることができることはわかっています。問題は、それを使用して、値を比較するifステートメントの混乱を手動で書くことです。

データの例:

seven/7
4for
8 eight?
five!

その行の文字数が最も少ないため、4forが返されます。

私の場合、複数の行が最短の場合、単一の行が返されます。最小の長さであれば、どちらを選択してもかまいません。しかし、他の状況で他のユーザーに両方の方法を示すことに害があるとは思いません。

Perlの方法。同じ最短の長さの行が多数ある場合、このアプローチではそのうちの1つだけが出力されることに注意してください。

Perl -lne '$m//=$_; $m=$_ if length()<length($m); END{print $m if $.}' file 

説明

  • Perl -lne-nは「入力ファイルを1行ずつ読み取る」ことを意味し、-lを指定すると、後続の改行が各入力行から削除され、改行が各print呼び出しに追加されます。 -eは、各行に適用されるスクリプトです。
  • $m//=$_$mが定義されていない限り、$_を現在の行($m)に設定します。 //=演算子は、Perl 5.10.0以降で使用できます。
  • $m=$_ if length()<length($m)$mの現在の値の長さが現在の行の長さより大きい場合、現在の行($_)を$mとして保存します。
  • END{print $m if $.}:すべての行が処理されたら、最短の行である$mの現在の値を出力します。 if $.は、行番号($.)が定義されている場合にのみこれが発生することを保証し、空白の入力で空の行が出力されるのを防ぎます。

または、ファイルがメモリに収まるほど小さいので、次のようにできます。

Perl -e '@K=sort{length($a) <=> length($b)}<>; print "$K[0]"' file 

説明

  • @K=sort{length($a) <=> length($b)}<><>これは、要素がファイルの行である配列です。 sortはそれらを長さに従ってソートし、ソートされた行は配列@Kとして保存されます。
  • print "$K[0]":配列@Kの最初の要素を出力:最短の行。

all最短の行を印刷したい場合は、

Perl -e '@K=sort{length($a) <=> length($b)}<>; 
         print grep {length($_)==length($K[0])}@K; ' file 
13
terdon

最初に見つかった最小行を出力するためのawkソリューションのバリアントは次のとおりです。

awk '
  NR==1 || length<len {len=length; line=$0}
  END {print line}
'

すべての最小行を出力するために、1つの条件で単純に拡張できます。

awk '
  length==len {line=line ORS $0}
  NR==1 || length<len {len=length; line=$0}
  END {print line}'
'
18
Janis

sqlite3

sqlite3 <<EOT
CREATE TABLE file(line);
.import "data.txt" file
SELECT line FROM file ORDER BY length(line) LIMIT 1;
EOT
18
FloHimself

Pythonはかなり簡潔になっており、コードはTinで言うことを実行します。

python -c "import sys; print min(sys.stdin, key=len),"

最後のコンマはあいまいです、私は認めます。これは、printステートメントが改行を追加するのを防ぎます。さらに、これをPython 3で記述して、次のような0行をサポートできます。

python3 -c "import sys; print(min(sys.stdin, key=len, default='').strip('\n'))"

12
Steve Jessop

私は常に純粋なシェルスクリプト(execなし!)を使用したソリューションが大好きです。

#!/bin/bash
min=
is_empty_input="yes"

while IFS= read -r a; do
    if [ -z "$min" -a "$is_empty_input" = "yes" ] || [ "${#a}" -lt "${#min}" ]; then
        min="$a"
    fi
    is_empty_input="no"
done

if [ -n "$a" ]; then
    if [ "$is_empty_input" = "yes" ]; then
        min="$a"
        is_empty_input="no"
    else
        [ "${#a}" -lt "${#min}" ] && min="$a"
    fi
fi

[ "$is_empty_input" = "no" ] && printf '%s\n' "$min"

入力のNULバイトに問題があります。そう、 printf "ab\0\0\ncd\n" | bash this_scriptabではなくcdを出力します。

10
yaegashi

ここでは純粋なzshソリューション(fileから、最小の長さのすべての行を出力します):

IFS=$'\n'; print -l ${(M)$(<file):#${~${(o@)$(<file)//?/?}[1]}}

入力例:

seven/7
4for
8 eight?
five!
four

出力は次のとおりです。

4for
four

簡単な説明が必要だと思います:-)


最初に、内部フィールド区切り文字を改行に設定します。

IFS=$'\n';

これまでのところ、良い、今は難しい部分。 printは、-lフラグを使用して、スペースではなく改行で区切られた結果を出力します。

では、内側から始めます。

$(<file)

ファイルは1行ずつ読み込まれ、配列として扱われます。次に:

${(o@)...//?/?}

oフラグは、結果を昇順で並べる必要があることを示します。@は、結果も配列として扱うことを意味します。 (//?/?)の後ろの部分は、すべての文字を?に置き換える置換です。今:

${~...[1]}

最初の配列要素[1]を使用します。これは最短で、今の場合は????です。

${(M)$(<file):#...}

マッチングは各配列要素に対して個別に実行され、一致しない配列要素は削除されます(M)。 ????(4文字)に一致する各要素は配列に残ります。したがって、残りの要素は4文字(最短)の要素です。

編集:最短の行が1つだけ必要な場合、この変更されたバージョンは最初の行を印刷します。

IFS=$'\n'; print -l ${${(M)$(<file):#${~${(o@)$(<file)//?/?}[1]}}[1]}
9
chaos
_tr -c \\n 1 <testfile |   #first transform every [^\n] char to a 1
grep -nF ''           |   #next get line numbers
paste -d: - testfile  |   #then paste it together with itself
sort  -t: -nk2,2          #then sort on second field
_

...そして勝者は... 2行目です。

_2:1111:4for
4:11111:five!
1:1111111:seven/7
3:11111111:8 eight?
_

しかし、それに関する問題は、すべての行が機能するためには、長さが2倍を超える必要があることです。そのため、LINE_MAXは事実上半分になります。原因はそれが使用していることです-何、ベース1? -ラインの長さを表します。同様の、そしておそらくもっと整然としたアプローチは、その情報をストリームで圧縮することです。私が思いつくこれらの線に沿った最初のアイデアは、私がそれをunexpandすべきであるということです:

_tr -c \\n \  <testfile    |   #transform all [^\n] to <space>
unexpand -t10             |   #squeeze every series of 10 to one tab
grep -nF ''               |   #and get the line numbers
sed    's/:/!d;=;:/;h;:big    #sed compares sequential lines
$P;$!N; /\(:[^ ]*\)\( *\)\n.*\1.*\2/!D     #newest line is shorter or...
        g;/:./!q;b big'   |   #not; quit input entirely for blank line
sed -f - -e q testfile        #print only first occurrence of shortest line
_

それは印刷します...

_2
4for
_

もう1つ、単にsed

_sed -n '/^\n/D;s/\(.\)\(\n.*\)*/\1/g
$p;h;   s// /g;G;x;n;//!g;H;s// /g
G;      s/^\( *\)\(\n \1 *\)\{0,1\}\n//
D'      <infile >outfile
_

構文は標準に準拠していますが、古いsed\(reference-group\)\{counts\}を正しく処理することを保証するものではありません。

それは基本的に同じ正規表現を繰り返し入力に適用します-これはそれらをコンパイルするとき非常に有益です。そのパターンは次のとおりです。

_\(.\)\(\n.*\)*
_

さまざまな方法でさまざまな文字列に一致します。例えば:

_string1\nstring2\nstring3
_

...は_\1_のsと一致し、_''_は_\2_のnull文字列と一致します。

_1\nstring2\nstring3
_

... _1_の_\1_および_\nstring2\nstring3_の_\2_と一致します

_\nstring2\nstring3
_

... _\n_の_\1_および_''_の_\2_のnull文字列と一致します。これは、パターンスペースの先頭で_\n_ ewlineが発生する可能性がある場合は問題になりますが、これを防ぐために_/^\n/D_および_//!g_コマンドが使用されます。私は_[^\n]_を使用しましたが、この小さなスクリプトの他のニーズにより移植性が懸念され、誤って解釈されることが多い多くの方法に満足できませんでした。さらに、_._の方が高速です。

_\nstring2
string1
_

... _\n_で_\1_およびsに再度一致し、両方が_''_で_\2_ null文字列を取得します。空の行はまったく一致しません。

パターンが適用されるときglobally 2つのバイアス-左端の標準バイアスと右下の_\n_ ewlineバイアスの両方-がバランスを取り、スキップを実行します。いくつかの例:

_s/\(.\)\(\n.*\)*/\1:\2/g
s/\(.\)\(\n.*\)*/\2\1:/g
s/\(.\)\(\n.*\)*/\1: /g
s/\(.\)\(\n.*\)*/ :\2/g
_

...すべてが次の文字列に適用された場合(連続していない) ...

_string1\nstring2
_

...に変換します...

_s:t:r:i:n:g:1:\nstring2
s:t:r:i:n:g:\nstring21:
s:t:r:i:n:g:1: 
 : : : : : : :\nstring2
_

基本的に私は正規表現を使用して、適用するパターンスペースの最初の行のみを常に処理します。これにより、テストループを使用せずに、保持されている最短一致までの行と最新の行の両方の2つの異なるバージョンを両立させることができます。適用されるすべての置換は、パターン空間全体を一度に処理します。

リテラル文字列と文字列の比較には異なるバージョンが必要です。したがって、すべての文字が等しいことが保証されている各行のバージョンが必要です。しかし、もちろん、どちらか一方が実際に入力で最も早く発生する最短の行になる場合、出力に出力される行はおそらく、元のバージョンの行であるはずです-比較のためにサニタイズ/均質化したものではありません。そして、それぞれに2つのバージョンが必要です。

残念なことに、同じように処理するために多くのバッファを切り替える必要があります。ただし、少なくともどちらのバッファも、最新の状態を維持するために必要な4行を超えることはないため、ひどいことではありません。

とにかく、各サイクルで最初に起こるのは記憶された行の変換です-実際に保存される唯一のコピーは文字通りのオリジナルなので-に...

_^               \nremembered line$
_

...その後、next入力行が古いバッファを上書きします。少なくとも1つの文字が含まれていない場合、事実上無視されます。最初の空白行でquitを実行する方がはるかに簡単ですが、まあ、私のテストデータにはたくさんのものがあり、複数の段落を処理したいと思っていました。

したがって、文字が含まれている場合、そのリテラルバージョンは記憶された行に追加され、間隔を空けた比較バージョンは次のようにパターンスペースの先頭に配置されます。

_^   \n               \nremembered line\nnew$
_

最後に、置換がそのパターンスペースに適用されます。

_s/^\( *\)\(\n \1 *\)\{0,1\}\n//
_

したがって、記憶された行を含めるのに必要なスペースに改行が収まる場合は、少なくとも1つの文字を使用して、最初の2行が置き換えられます。

結果に関係なく、パターンスペースの最初の行は、サイクルの終わりに、再び開始する前に常にDeletedです。つまり、新しい行が最後の行より短い場合、文字列は...

_new
_

...サイクルの最初の置換に送り返され、常に最初の改行文字のみが削除されるため、全体が残ります。しかし、それが文字列でない場合...

_remembered line\nnew
_

...代わりに次のサイクルが開始され、最初の置換によってストリングが削除されます...

_\nnew
_

...毎回。

最後の行では、記憶された行が標準出力に出力されます。したがって、指定されたサンプルデータの場合、次のように出力されます。

_4for
_

しかし、真剣に、trを使用してください。

8
mikeserv

試してください:

awk '{ print length, $0 }' testfile | sort -n | cut -d" " -f2- | head -1

アイデアはawkを使用して、最初に各行の長さを出力することです。これは次のように表示されます。

echo "This is a line of text" | awk '{print length, $0}'
22 This is a line of text

次に、文字カウントを使用して行をsortcutで並べ替え、カウントを削除し、headで最初の行(文字数が最も少ない行)を保持します。もちろん、この場合、tailを使用して、最も多くの文字を含む行を取得できます。

(これは この答え から採用されました)

7
Bichoy

POSIX awkの場合:

awk 'FNR==1{l=$0;next};length<length(l){l=$0};END{print l}' file
5
cuonglm

@mikeservのアイデアの一部を借用する:

< testfile sed 'h;s/./:/g;s/.*/expr length "&"/e;G;s/\n/\t/' | \
sort -n | \
sed -n '1s/^[0-9]+*\t//p'

最初のsedは次のことを行います。

  • hは、元の行を保留バッファーに保存します
  • 行のすべての文字を:に置き換えます-これはコードインジェクションの危険を取り除くためです
  • 行全体をexpr length "whole line"に置き換えます-これは評価される可能性のあるシェル式です
  • sへのeコマンド は、パターンスペースを評価して結果を戻すGNU sed拡張子ですパターンスペースで。
  • Gは、改行と保留スペースの内容(元の行)をパターンスペースに追加します
  • 最後のsは改行をタブに置き換えます

文字数は各行の先頭の数字になりました。そのため、sort -nは行の長さでソートされます。

最後のsedは、最初の(最短の)行と行の長さ以外をすべて削除し、結果を出力します。

3
Digital Trauma

すべてが1つのsed式で可能であることに気付きました。それはきれいではありません:

$ sed '1h;s/.*/&\n&/;G;:l;s/\n[^\n]\([^\n]*\)\n[^\n]/\n\1\n/;tl;/\n\n/{s/\n.*//;x};${x;p};d' testfile
4for
$ 

これを分解すると:

1h            # save line 1 in the hold buffer (shortest line so far)
s/.*/&\n&/    # duplicate the line with a newline in between
G             # append newline+hold buffer to current line
:l            # loop start
s/\n[^\n]\([^\n]*\)\n[^\n]/\n\1\n/
              # attempt to remove 1 char both from current line and shortest line
tl            # jump back to l if the above substitution succeeded
/\n\n/{       # matches if current line is shorter
  s/\n.*//    # remove all but original line
  x           # save new shortest line in hold buffer
}
${            # at last line
  x           # get shortest line from hold buffer
  p           # print it
}
d             # don't print any other lines

OS XでsedされたBSDは、改行で少し厄介です。このバージョンは、BSDとsedのGNUバージョンの両方で機能します。

$ sed -e '1h;G;s/\([^\n]*\)\(\n\)\(.*\)/\1\2\1\2\3/;:l' -e 's/\(\n\)[^\n]\([^\n]*\n\)[^\n]/\1\2/;tl' -e '/\n\n/{s/\n.*//;x;};${x;p;};d' testfile
4for
$

これは、ベストプラクティスの回答を提供するための真剣な試みというよりは、「可能性があるため」の回答の方が多いことに注意してください。コードコールをやりすぎたことを意味していると思います

3
Digital Trauma

空白行が最短の行とは見なされず、空白行が存在すると想定すると、次の純粋なAWKが機能します。

awk '
    {
        len   = length;
        a[$0] = len
    }
    !len { next }
    !min { min = len }
    len < min { min = len }
    END {
        for (i in a)
            if (min == a[i])
                print i
    }
' infile.txt
2
snth

最初の最短の行だけを取得するには:

f=file; sed -n "/^$(sed 's/./1/g' $f | sort -ns | sed 's/././g;q')$/{p;q}" $f

最短のリントをすべて取得するには、{p;q}からp


別の方法(やや珍しい)はsortに実際の長さでソートを実行させることです。短いラインでも比較的遅く、ラインが長くなると劇的に遅くなります。
しかし、重複するキーによるソートのアイデアは非常に興味深いと思います。他の人も興味深い/参考になると思う場合に備えて投稿しています。

使い方:
同じキーの長さバリアントで並べ替え– key 1行全体にまたがる
連続する各キーバリアントは、キーの長さを1文字ずつインクリメントし、ファイルの最長の行の長さになります(wc -L

最初の(ソートされた)最短の行だけを取得するには:

f=file; sort -t'\0' $(seq -f "-k1.%0.0f" $(<"$f" wc -L) -1 1) "$f" | head -n1

これは次と同じです:

f=file.in; 
l=$(<"$f" wc -L)
k=$(seq -f "-k1.%0.0f" $l -1 1) 
sort -st'\0' $k "$f" | head -n1
2
Peter.O

別のPerlソリューション:行を配列のハッシュに格納します。ハッシュキーは行の長さです。次に、最小のキーで行を印刷します。

Perl -MList::Util=min -ne '
    Push @{$lines{ length() }}, $_;
} END {
    print @{$lines{ min keys %lines }};
' sample 
4for
2
glenn jackman

並べ替えの使用についてはどうですか?

awk '{ print length($0) "\t" $0 }' input.txt | sort -n | head -n 1 | cut -f2-
2
Gaurav

GNU awk

gawk '
    {
         a[length]=$0
    };
    END
    {
        PROCINFO["sorted_in"]="@ind_num_asc";
        for (i in a)
        {
            print a[i]; 
            exit
        }
    }
    ' file
  • 各行を行の長さでインデックス付けされた配列に読み込みます。

  • セットする PROCINFO["sorted_in"]から@ind_num_asc配列スキャン を強制的に配列インデックスで並べ替え、数値でソートします

  • 上記の方法でPROCINFOを設定すると、最小の長さの線が配列の走査で最初に選択されます。したがって、配列から最初の要素を出力して終了します

これにはnlognであるという欠点がありますが、他のアプローチのいくつかは時間内にnです

1
iruvar

sedまたはawkを使用しない、中レベルのシェルツールメソッド:

f=inputfile
head -n $(xargs -d '\n' -L 1 -I % sh -c 'exec echo "%" | wc -c' < $f | 
          cat -n | sort -n -k 2 | head -1 | cut -f 1)  $f | tail -1
1
agc