web-dev-qa-db-ja.com

スペースを含む行の長さでテキストファイルを並べ替える

このようなCSVファイルがあります

 AS2345、ASDF1232、Mr。Plain Example、110 Binary ave。、アトランティス、RI、12345、(999)123-5555、1.56 
 AS2345、ASDF1232、Mrs。Plain Example、1121110 Ternary st 。 110 Binary ave ..、Atlantis、RI、12345、(999)123-5555,1.56 
 AS2345、ASDF1232、Mr. Plain Example、110 Binary ave。、Liberty City、RI、12345、(999)123 -5555,1.56 
 AS2345、ASDF1232、Mr. Plain Example、110 Ternary ave。、Some City、RI、12345、(999)123-5555,1.56 

スペースを含む行の長さでソートする必要があります。次のコマンドにはスペースが含まれていませんが、それを修正する方法はありますか?

cat $@ | awk '{ print length, $0 }' | sort -n | awk '{$1=""; print $0}'
121
gnarbarian

回答

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

または、同じ長さの行の元の(おそらく意図しない)サブソートを行うには:

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

どちらの場合も、最終的なカットのためにawkから離れることで、指定された問題を解決しました。

一致する長さの行-同点の場合の処理​​:

質問では、一致する長さの行に対してさらにソートする必要があるかどうかは指定されませんでした。これは望ましくないと想定し、-s--stable)を使用して、このような行が相互にソートされないようにし、入力で発生する相対的な順序でそれらを保持することを提案しました。

(これらのタイのソートをより詳細に制御したい場合は、ソートの--keyオプションを参照してください。)

質問の試みられた解決策が失敗する理由(awkラインの再構築):

以下の違いに注意することは興味深いです:

echo "hello   awk   world" | awk '{print}'
echo "hello   awk   world" | awk '{$1="hello"; print}'

彼らはそれぞれ得ます

hello   awk   world
hello awk world

(gawk's)マニュアルの関連セクション は、1つのフィールドを変更するとawkが(セパレーターなどに基づいて)$ 0全体を再構築することを脇に置いて言及しているだけです。クレイジーな振る舞いではないと思います。これがあります:

「最後に、フィールドとOFSの現在の値を使用して、awkにレコード全体を再構築させると便利な場合があります。これを行うには、一見無害な割り当てを使用します。」

 $1 = $1   # force record to be reconstituted
 print $0  # or whatever else with $0

「これにより、awkは強制的にレコードを再構築します。」

等しい長さのいくつかの行を含むテスト入力:

aa A line   with     MORE    spaces
bb The very longest line in the file
ccb
9   dd equal len.  Orig pos = 1
500 dd equal len.  Orig pos = 2
ccz
cca
ee A line with  some       spaces
1   dd equal len.  Orig pos = 3
ff
5   dd equal len.  Orig pos = 4
g
192
neillb

neillbからのAWKソリューション は、本当にawkを使用したい場合に最適であり、面倒な理由を説明しますが、必要なのは仕事を迅速に行い、あなたが何をするかに気をつけて、一つの解決策は、Perlのsort()関数をカスタムcaparisonルーチンとともに使用して、入力行を反復することです。ライナーは次のとおりです。

Perl -e 'print sort { length($a) <=> length($b) } <>'

これを必要な場所でパイプラインに配置し、(catまたはシェルリダイレクトから)STDINを受け取るか、別の引数としてファイル名をPerlに渡してファイルを開かせます。

私の場合、最初に最も長い行が必要だったので、比較で$a$bを交換しました。

20
Caleb

代わりに次のコマンドを試してください。

awk '{print length, $0}' your-file | sort -n | cut -d " " -f2-
14
anubhava

ベンチマーク結果

以下は、この質問に対するその他の回答からのソリューションにわたるベンチマークの結果です。

試験方法

  • 高速マシンでの10回の連続実行、平均
  • Perl 5.24
  • awk 3.1.5(gawk 4.1.0の時間は〜2%高速でした)
  • 入力ファイルは550MB、600万行の怪物です(British National Corpus txt)

結果

  1. CalebのPerlソリューション 11.2秒かかりました
  2. my Perl solution 11.6秒かかりました
  3. neillbのawkソリューション #1は20秒かかりました
  4. neillbのawkソリューション #2は23秒かかりました
  5. anubhavaのawkソリューション 24秒かかりました
  6. ジョナサンのawkソリューション 25秒かかりました
  7. Fretzのbashソリューション は、awkソリューションよりも400倍長くかかります(100000行の切り捨てられたテストケースを使用)。それはうまく機能し、永遠にかかります。

追加のPerlオプション

また、別のPerlソリューションを追加しました。

Perl -ne 'Push @a, $_; END{ print sort { length $a <=> length $b } @a }' file
7
Chris Koknat

純粋なバッシュ:

declare -a sorted

while read line; do
  if [ -z "${sorted[${#line}]}" ] ; then          # does line length already exist?
    sorted[${#line}]="$line"                      # element for new length
  else
    sorted[${#line}]="${sorted[${#line}]}\n$line" # append to lines with equal length
  fi
done < data.csv

for key in ${!sorted[*]}; do                      # iterate over existing indices
  echo -e "${sorted[$key]}"                       # echo lines with equal length
done
5
Fritz G. Mehner

length()関数にはスペースが含まれます。私はあなたのパイプラインをほんの少し調整します( UUOC の回避を含む)。

awk '{ printf "%d:%s\n", length($0), $0;}' "$@" | sort -n | sed 's/^[0-9]*://'

sedコマンドは、awkコマンドによって追加された数字とコロンを直接削除します。または、awkからフォーマットを維持します:

awk '{ print length($0), $0;}' "$@" | sort -n | sed 's/^[0-9]* //'
3

POSIX Awkの場合:

{
  c = length
  m[c] = m[c] ? m[c] RS $0 : $0
} END {
  for (c in m) print m[c]
}

2
Steven Penny

1)純粋なawkソリューション。行の長さが1024を超えることはできないと仮定しましょう

猫のファイル名| awk 'BEGIN {min = 1024; s = "";} {l = length($ 0); if(l <min){min = l; s = $ 0;}} END {print s} '

2)すべての行にたった1つの単語があると仮定した1つのライナーbashソリューションですが、すべての行に同じ数の単語がある場合は修正できます。

LINES = $(cat filename); $ LINESのk do printf "$ k";エコー$ k | wc -L;完了| sort -k2 | head -n 1 |カット-d "" -f1

2

ファイルに数値で始まる行が含まれている場合、これらのソリューションは機能しないことがわかりました。これらのソリューションは、カウントされたすべての行とともに数値的にソートされるためです。解決策は、sort-g(数値ソート)の代わりに-n(一般数値ソート)フラグを付けることです。

awk '{ print length, $0 }' lines.txt | sort -g | cut -d" " -f2-

以下は、長さで行をソートするマルチバイト互換の方法です。以下が必要です。

  1. wc -mが利用可能です(macOSにはあります)。
  2. 現在のロケールは、LC_ALL=UTF-8を設定するなどして、マルチバイト文字をサポートしています。これは、.bash_profileで設定するか、単に次のコマンドの前に追加することで設定できます。
  3. testfileには、ロケールに一致する文字エンコード(UTF-8など)があります。

完全なコマンドは次のとおりです。

cat testfile | awk '{l=$0; gsub(/\047/, "\047\"\047\"\047", l); cmd=sprintf("echo \047%s\047 | wc -m", l); cmd | getline c; close(cmd); sub(/ */, "", c); { print c, $0 }}' | sort -ns | cut -d" " -f2-

パートごとの説明:

  • l=$0; gsub(/\047/, "\047\"\047\"\047", l);←awk変数lの各行のコピーを作成し、'ごとに二重エスケープするため、行をシェルコマンドとして安全にエコーできます(\047は8進数の単一引用符です)表記法)。
  • cmd=sprintf("echo \047%s\047 | wc -m", l);←これは実行するコマンドで、エスケープされた行をwc -mにエコーします。
  • cmd | getline c;←コマンドを実行し、返される文字カウント値をawk変数cにコピーします。
  • close(cmd);←シェルコマンドへのパイプを閉じて、1つのプロセスで開いているファイルの数がシステム制限に達しないようにします。
  • sub(/ */, "", c);wcによって返される文字カウント値から空白を削除します。
  • { print c, $0 }←行の文字カウント値、スペース、および元の行を出力します。
  • | sort -ns←行を(先頭に追加された文字カウント値で)数値的にソートし(-n)、安定したソート順を維持します(-s)。
  • | cut -d" " -f2-←付加された文字カウント値を削除します。

各行に対してサブコマンドを実行する必要があるため、低速です(高速のMacbook Proでは1秒あたり160行のみ)。

または、gawk(バージョン3.1.5以降、gawkはマルチバイト対応)だけでこれを行うだけで、大幅に高速になります。すべてのエスケープと二重引用符を使用してawkからシェルコマンドに安全に行を渡すのは非常に困難ですが、これは追加のソフトウェアのインストールを必要としない唯一の方法です(gawkはデフォルトでは利用できませんマックOS)。

1
Quinn Comendant