3

かなり大きなcsvファイルには少し問題があります。単純な bash/awk スクリプトを作成することはできますが、awk/bash プログラミングの経験が限られているため、この問題は困難です。

問題:

  • 私のファイルはすべてフォルダにあります。フォルダーには、ペアごとにトリミングする必要がある csv ファイルが偶数個あります (この意味で説明します)。ファイルの名前は、f1L、f1R、f2L、f2R、f3L、f3R、...、fnL、fnR のようになります。

  • ファイルはペアで読み取る必要があります。f1R と f1L。f2Rとf2Lなど

  • ファイルには、コンマで区切られた 2 つのフィールドがあります。f1L (ファイルの開始/終了) と f1R は次のようになります。

f1L (START)
1349971210, -0.984375 
1349971211, -1.000000 

f1R (START) 
1349971206, -0.015625
1349971207, 0.000000

f1L (END)
1350230398, 0.500000
1350230399, 0.515625

f1R (END) 
1350230402, 0.484375
1350230403, 0.515625

awk でやりたいことは次のとおりです。

  1. レコード 1、f1L のフィールド 1 (つまり 1349971210) を読み取り、次にレコード 1、f1R のフィールド 1 (つまり 1349971206) を読み取ります。次に、両方の値の最大値を取ります (つまり、x1 = 1349971210)。
  2. 最後のレコード、f1L のフィールド 1 (つまり、1350230399) を読み取り、次に最後のレコード、f1R のフィールド 1 (つまり、1350230403) を読み取ります。次に、最小値を取ります (つまり、x2 = 1350230399)。
  3. 次に、f1L と f1R 内の x1 以上と x2 以下の間のすべての行を抽出して、同じ名前で再保存します。
  4. ディレクトリ内のすべてのペアに対してプロセスを繰り返します。

仕事を終わらせるために bash/awk を使った小さなスクリプトの提案があるかどうか疑問に思いました。

4

3 に答える 3

1

でそれを達成する単純な方法。ここでは効率をまったく求めていません。エラー チェックはありません (まあ、必須の最低限のチェックだけです)。

このスクリプトにmyscriptという名前を付けます。2 つのパラメーター (ファイルfxLおよびfxR ) を取ります。

#!/bin/bash

tmp=''

die() {
    echo >&2 "$@"
    exit 1
}

on_exit() {
    [[ -f $tmpL ]] && rm -- "$tmpL"
    [[ -f $tmpR ]] && rm -- "$tmpR"
}

last_non_blank_line() {
   sed -n -e $'/^$/ !h\n$ {x;p;}' "$1"
}

(($#==2)) || die "script takes two arguments"

fL=$1
fR=$2

[[ -r "$fL" && -w "$fL" ]] || die "problem with file \`$fL'"
[[ -r "$fR" && -w "$fR" ]] || die "problem with file \`$fR'"

# read record1, line1 of fL and fR
IFS=, read min _ < "$fL"
[[ $min =~ ^[[:digit:]]+$ ]] || die "first line of \`$fL' has a bad record"
IFS=, read t _ < "$fR"
[[ $t =~ ^[[:digit:]]+$ ]] || die "first line of \`$fR' has a bad record"
((t>min)) && ((min=t))

# read record1, last line of fL and fR
IFS=, read max _ < <( last_non_blank_line "$fL")
[[ $max =~ ^[[:digit:]]+$ ]] || die "last line of \`$fL' has a bad record"
IFS=, read t _ < <(last_non_blank_line "$fR")
[[ $t =~ ^[[:digit:]]+$ ]] || die "last line of \`$fR' has a bad record"
((t<max)) && ((max=t))

# create tmp files
tmpL=$(mktemp --tmpdir) || die "can't create tmp file"
tmpR=$(mktemp --tmpdir) || die "can't create tmp file"

trap 'on_exit' EXIT

# Read fL line by line, and only keep those
# the first record of which is between min and max
while IFS=, read a b; do
    [[ $a =~ ^[[:digit:]]+$ ]] && ((a<=max)) && ((a>=min)) && echo "$a,$b"
done < "$fL" > "$tmpL"
mv -- "$tmpL" "$fL"

# Same with fR:
while IFS=, read a b; do
    [[ $a =~ ^[[:digit:]]+$ ]] && ((a<=max)) && ((a>=min)) && echo "$a,$b"
done < "$fR" > "$tmpR"
mv -- "$tmpR" "$fR"

そしてそれを次のように呼び出します:

$ myscript f1L f1R

最初にスクラッチ ファイルで使用してください。無保証!自己責任!

警告。スクリプトは比較に演算を使用するため、各ファイルの各行の最初のレコードは、が処理する範囲の整数であると想定されます。


編集。演算を使用する上記の方法は使用できません。非常におもしろい方法は、に必要なすべての操作 (最初の行を取得し、最後の行を取得し、ファイルを開くなど) を実行させ、算術部分にこれにより、数値のサイズにまったく制限されなくなり ( は任意の精度を使用します)、浮動小数点数は大歓迎です! 例えば:

#!/bin/bash

tmp=''

die() {
    echo >&2 "$@"
    exit 1
}

on_exit() {
    [[ -f $tmpL ]] && rm -- "$tmpL"
    [[ -f $tmpR ]] && rm -- "$tmpR"
}

last_non_blank_line() {
   sed -n -e $'/^$/ !h\n$ {x;p;}' "$1"
}

(($#==2)) || die "script takes two arguments"

fL=$1
fR=$2

[[ -r "$fL" && -w "$fL" ]] || die "problem with file \`$fL'"
[[ -r "$fR" && -w "$fR" ]] || die "problem with file \`$fR'"

# read record1, line1 of fL and fR
IFS=, read a _ < "$fL"
IFS=, read b _ < "$fR"
min=$(bc <<< "if($b>$a) { print \"$b\" } else { print \"$a\" }" 2> /dev/null)
[[ -z $min ]] && die "problem in first line of files \`$fL' or \`$fR'"

# read record1, last line of fL and fR
IFS=, read a _ < <( last_non_blank_line "$fL")
IFS=, read b _ < <(last_non_blank_line "$fR")
max=$(bc <<< "if($b<$a) { print \"$b\" } else { print \"$a\" }" 2> /dev/null)
[[ -z $max ]] && die "problem in last line of files \`$fL' or \`$fR'"

# create tmp files
tmpL=$(mktemp --tmpdir) || die "can't create tmp file"
tmpR=$(mktemp --tmpdir) || die "can't create tmp file"

trap 'on_exit' EXIT

# Read fL line by line, and only keep those
# the first record of which is between min and max
while read l; do
    [[ $l =~ ^[[:space:]]*$ ]] && continue
    r=${l%%,*}
    printf "if(%s>=$min && %s<=$max) { print \"%s\n\" }\n" "$r" "$r" "$l"
done < "$fL" | bc > "$tmpL" || die "Error in bc while doing file \`$fL'"

# Same with fR:
while read l; do
    [[ $l =~ ^[[:space:]]*$ ]] && continue
    r=${l%%,*}
    printf "if(%s>=$min && %s<=$max) { print \"%s\n\" }\n" "$r" "$r" "$l"
done < "$fR" | bc > "$tmpR" || die "Error in bc while doing file \`$fR'"

mv -- "$tmpL" "$fL"
mv -- "$tmpR" "$fR"
于 2013-06-24T18:38:58.847 に答える
1

必要なすべての健全性チェックを含め、ディスク I/O を最小限に抑えようとしました (ファイルが十分に大きいため、ファイルを読み取ることが時間制限要因であると仮定します)。また、ファイル全体をメモリに読み込む必要はありません (ファイルが使用可能な RAM よりもさらに大きい場合)。

ただし、これは非常に基本的なダミー入力を使用して試行しただけなので、テストして問題があれば報告してください。

最初に、1 つのペア (f...L ファイル名で識別) をトリミングするスクリプトを作成しました。

#!/bin/sh

#############    
# trim_pair #
#-----------#############################
# given fXL file path, trim fXL and fXR #
#########################################

#---------------# 
# sanity checks #
#---------------#

# error function
error(){
 echo >&2 "$@"
 exit 1
}

# argument given?
[[ $# -eq 1 ]] || \
 error "usage: $0 <file>"
LFILE="$1"

# argument format valid?
[[ `basename "$LFILE" | egrep '^f[[:digit:]]+L$'` ]] || \
 error "invalid file name: $LFILE (has to match /^f[[:digit:]]+L$/)"
RFILE="`echo $LFILE | sed s/L$/R/`" # is there a better POSIX compliant way?

# files exists?
[[ -e "$LFILE" ]] || \
 error "file does not exist: $LFILE"
[[ -e "$RFILE" ]] || \
 error "file does not exist: $RFILE"

# files readable?
[[ -r "$LFILE" ]] || \
 error "file not readable: $LFILE"
[[ -r "$RFILE" ]] || \
 error "file not readable: $RFILE"

# files writable?
[[ -w "$LFILE" ]] || \
 error "file not writable: $LFILE"
[[ -w "$RFILE" ]] || \
 error "file not writable: $RFILE"

#------------------#
# create tmp files #
# & ensure removal #
#------------------#

# cleanup function
cleanup(){
 [[ -e "$LTMP" ]] && rm -- "$LTMP"
 [[ -e "$RTMP" ]] && rm -- "$RTMP"
}

# cleanup on exit
trap 'cleanup' EXIT

#create tmp files
LTMP=`mktemp --tmpdir` || \
 error "tmp file creation failed"
RTMP=`mktemp --tmpdir` || \
 error "tmp file creation failed"

#----------------------#
# process both files   #
# prepended by their   #
# first and last lines #
#----------------------#

# extract first and last lines without reading the whole files twice
{
 head -q -n1 "$LFILE" "$RFILE"  # no need to read the whole files
 tail -q -n1 "$LFILE" "$RFILE"  # no need to read the whole files
} | awk -F, '
 NF!=2{
  print "incorrect file format: record "FNR" in file "FILENAME > "/dev/stderr"
  exit 1    
 }
 NR==1{                         # read record 1,
  x1=$1                         # field 1 of L,
  next                          # then read
 }
 NR==2{                         # record 1 of R,
  x1=$1>x1?$1:x1                # field 1 & take the max,
  next                          # then
 }
 NR==3{                         # read last record,
  x2=$1                         # field 1 of L,
  next                          # then
 }
 NR==4{                         # last record of R
  x2=$1>x2?$1:x2                # field 1 & take the max
  next
 }
 FILENAME!="-"&&NR<5{
  print "too few lines in input" > "/dev/stderr"
 }
 FNR==1{
  outfile=FILENAME~/L$/?"'"$LTMP"'":"'"$RTMP"'"
 }
 $1>=x1&&$1<=x2{
  print > outfile
 }
' - "$LFILE" "$RFILE" || \
 error "error while trimming"

#-----------------------#
# re-save trimmed files #
# under the same names  #
#-----------------------#

mv -- "$LTMP" "$LFILE" || \
 error "cannot re-save $LFILE"
mv -- "$RTMP" "$RFILE" || \
 error "cannot re-save $RFILE"

ご覧のとおり、主なアイデアは、重要な行を使用して入力を前に追加し、head要求に従ってtail処理する前に使用することでした。awk

特定のディレクトリ内のすべてのファイルに対してそのスクリプトを呼び出すには、次のスクリプトを使用できます (上記ほどうまくいきませんが、自分でそのようなものを思い付くことができると思います)。

#!/bin/sh

############
# trim all #
#----------###################################
# find L files in current or given directory #
# and trim the corresponding file pairs      #
##############################################

TRIM_PAIR="trim_pair"   # path to the trim script for one pair

if [[ $# -eq 1 ]]
then
 WD="$1"
else
 WD="`pwd`"
fi

find "$WD"                         \
 -type f                           \
 -readable                         \
 -writable                         \
 -regextype posix-egrep            \
 -regex "^$WD/"'f[[:digit:]]+L'    \
 -exec "$TRIM_PAIR" "{}" \;

trim_pair スクリプトを使用するか、スクリプト内の変数をPATH調整する必要があることに注意してください。TRIM_PAIRtrim_all

于 2013-07-06T22:55:05.333 に答える