web-dev-qa-db-ja.com

sedやPerlなどを使用せずに、行の最後の3文字をトリミングします

次のようなデータを出力するシェルスクリプトがあります。

1234567890  *
1234567891  *

最後の3文字「*」を削除する必要があります。私はそれを介してそれを行うことができることを知っています

(whatever) | sed 's/\(.*\).../\1/'

しかし、速度を上げるためにsedを使いたくありません。常に最後の3文字と同じになります。

出力をクリーンアップする簡単な方法はありますか?

38
RubiCon10

すべてのデータが例のようにフォーマットされていると仮定すると、「 cut 」を使用して最初の列のみを取得します。

cat $file | cut -d ' ' -f 1  

または最初の10文字を取得します。

cat $file | cut -c 1-10
12
Larry Wang

以下は、sed OR awk ...

> echo 987654321 | rev | cut -c 4- | rev

987654

「カット」を使用した以前の例とは異なり、これは行の長さの知識を必要としません。

166
sitzen2k

このタスクでは、bashだけではsedより速くなることはありません。 bashで外部プロセスを起動することは、一般的には悪い考えですが、頻繁に行う場合のみです。

したがって、入力のeach行に対してsedプロセスを開始している場合は、心配です。しかし、あなたは違います。 onesedを開始するだけで、すべての作業が自動的に行われます。

ただし、次のsedがお使いのバージョンより少し高速になる場合があります。

(whatever) | sed 's/...$//'

これは、行全体を短いバージョンで置き換えるのではなく、各行の最後の3文字を削除するだけです。現在、より最新のREエンジンでコマンドを最適化できるかもしれませんが、なぜリスクを取るのでしょう。

正直に言うと、私がそれがより速くなると考えることができる唯一の方法については、あなた自身のCベースのフィルタープログラムを手作りすることです。 maysedよりも高速である唯一の理由は、処理の必要性に関する追加の知識を活用できるためです(sedは一般化を許可する必要があります)そのため、行列は遅くなる可能性があります)。

最適化のマントラを忘れないでください:"測定、推測しないでください!"


実際にbashで一度に1行ずつ実行したい場合(そして、それは悪い考えだと私は主張します)、次のように使用できます:

pax> line=123456789abc
pax> line2=${line%%???}
pax> echo ${line2}
123456789
pax> _

また、実際に必要速度が向上したかどうかを調べることもできます。行を1つの大きな塊として処理すると、sedが非常に高速であることがわかります。次を入力します。

#!/usr/bin/bash

echo This is a pretty chunky line with three bad characters at the end.XXX >qq1
for i in 4 16 64 256 1024 4096 16384 65536 ; do
    cat qq1 qq1 >qq2
    cat qq2 qq2 >qq1
done

head -20000l qq1 >qq2
wc -l qq2

date
time sed 's/...$//' qq2 >qq1
date
head -3l qq1

実行します。これが私の(まったく高速ではない)R40ラップトップの出力です。

pax> ./chk.sh
20000 qq2
Sat Jul 24 13:09:15 WAST 2010

real    0m0.851s
user    0m0.781s
sys     0m0.050s
Sat Jul 24 13:09:16 WAST 2010
This is a pretty chunky line with three bad characters at the end.
This is a pretty chunky line with three bad characters at the end.
This is a pretty chunky line with three bad characters at the end.

これは1秒未満で20,000行であり、1時間に1回しか実行されない処理に適しています。

30
paxdiablo
$ x="can_haz"
$ echo "${x%???}"
can_
11
A T

awksedはどちらも非常に高速ですが、重要だと思われる場合は、次のいずれかを使用してください。

削除する文字が常に文字列の末尾にある場合

echo '1234567890  *' | tr -d ' *'

文字列内の任意の場所に表示でき、最後にあるもののみを削除する場合

echo '1234567890  *' | rev | cut -c 4- | rev

すべてのコマンドのmanページで、何が起こっているのかが説明されます。

ただし、sedを使用する必要があると思います。

5
majhool

試すことができます

(whatever) | while read line; do echo $line | head --bytes -3; done;

head自体はsedcutよりも高速である必要があります。これは、正規表現や区切り文字の一致がないためですが、各行ごとにを個別に呼び出すとおそらくそれよりも重要です。

2
Aaron J Lang

注:この回答は冗談を意図していますが、実際には機能します...

#!/bin/bash
outfile="/tmp/$RANDOM"
cfile="$outfile.c"
echo '#include <stdio.h>
int main(void){int e=1;char c;while((c=getc(stdin))!=-1){if(c==10)e=1;if(c==32)e=0;if(e)putc(c,stdout);}}' >> "$cfile"
gcc -o "$outfile" "$cfile"
rm "$cfile"
cat somedata.txt | "$outfile"
rm "$outfile"

cat somedata.txtを別のコマンドに置き換えることができます。

2
icktoofay

スクリプトが常に10文字の行に3文字の余分な行を出力する場合(つまり、最初の10文字だけが必要な場合)は、次のように使用できます。

script | cut -c 1-10

不確実な数の非スペース文字が出力され、その後にスペースが続き、さらに2つの余分な文字(つまり、最初のフィールドだけが必要)が出力される場合、次を使用できます。

script | cut -d ' ' -f 1

...前のmajhoolのコメントのように。プラットフォームによっては、colrmもあります。これも、行が固定長の場合に機能します。

script | colrm 11
1
Zac Thompson

別の答えは、最後から3番目の文字がスペースであることに依存しています。これは、その位置の(ほぼ)すべての文字で機能し、「sedやPerlなどを使用せずに」実行します。

while read -r line
do
    echo ${line:0:${#line}-3}
done

行が固定長の場合、echoを次のように変更します。

echo ${line:0:9}

または

printf "%.10s\n" "$line"

しかし、これらはそれぞれdefinitelymuchsedより遅い。

1

カットや魔法は必要ありません。bashでは次のように文字列をカットできます。

  ORGSTRING="123456"
  CUTSTRING=${ORGSTRING:0:-3}
  echo "The original string: $ORGSTRING"
  echo "The new, shorter and faster string: $CUTSTRING"

http://tldp.org/LDP/abs/html/string-manipulation.html を参照してください

1
DusteD

スペースがない場合(またはスペースがある場合は区切り文字を変更する場合)は、最初の「フィールド」を印刷するためだけにawkを使用できます。

上記のフィールドをファイルに入れてこれを行いました

awk '{ print $1 }' < test.txt 
1234567890
1234567891

それが良いかどうかはわかりません。

0
Shawn D.

速度を上げるためにsed/awkを使いたくないのはどういうことですか? sed/awkは、ファイルを処理するためのシェルのwhile読み取りループよりも高速です。

$ sed 's/[ \t]*\*$//' file
1234567890
1234567891

$ sed 's/..\*$//' file
1234567890
1234567891

バッシュシェル付き

while read -r a b
do
 echo $a
done <file
0
ghostdog74