
現在、find出力に基づいてRコマンドを生成するスクリプトがあります。
#!/bin/bash
PATHX="/path/to/my/files"
find "${PATHX}" -maxdepth 1 -type f -name "*.csv" | while read d; do
FN=$(echo -n "${d}" | cut -d/ -f5 | cut -d. -f1)
echo "${FN}<-read.csv(\"${PATHX}/${FN}.csv\",header=TRUE)"
# <snip> etc .etc. etc.
echo "${FN}_2y<-tail(${FN}_log,730)"
done
これは素晴らしい作品です。しかし、Rコマンドを使用すると問題が発生しました。
df<-data.frame(list,of,columns,goes,here)
これを上記のfind / whileに統合する方法がわかりません。つまり、リストを出力する必要があります。${FN}_2年data.frame() 関数を入力してください。
たとえば、私のスクリプトが次のように出力されるとします。
- a_2y
- b_2y
- c_2y
df<-data.frame(a_2y,b_2y,c_2y) で終わる必要があります。
コメントの質問をより明確にするために、すべてのcsv入力が解析された後、最後に1つのdata.frameインスタンスのみが必要です。
答え1
名前を変数として収集fns
し、最後にその変数をエコーできます。パイプがあるので、変数をwhile / do / doneと同じサブシェルに保持する必要があります。${fns:1}
最初の追加コンマが削除された変数の部分文字列。
#!/bin/bash
PATHX="/path/to/my/files"
find "${PATHX}" -maxdepth 1 -type f -name "*.csv" |
( fns=
while read d; do
FN=$(echo -n "${d}" | cut -d/ -f3 | cut -d. -f1)
echo "${FN}<-read.csv(\"${PATHX}/${FN}.csv\",header=TRUE)"
# <snip> etc .etc. etc.
echo "${FN}_2y<-tail(${FN}_log,730)"
fns+=",${FN}_2y"
done
echo "df<-data.frame(${fns:1})"
)
答え2
この種の操作はシェルスクリプトで実行するのが簡単です(awk
配列をサポートするbashのようなものを使用している場合は、配列なしで何かを使用するよりも少し簡単です。引用符とワイルドカードを使用したり拡張したくありません) 。拡張する代わりにシェルスクリプトまたは)perl
sh
which
sh
perl
awk
たとえば、
#!/usr/bin/perl
use strict;
my $pathx='/path/to/my/files';
my $dh;
my @frames=();
# get list of .csv files from $pathx
opendir($dh, $pathx) || die "can't open directory '$pathx': $!\n";
my @csvfiles = grep { /\.csv$/ && -f "$pathx/$_" } readdir($dh);
closedir($dh);
foreach my $f (@csvfiles) {
my @fields=split(/\./,$f);
my $fn=$fields[@fields-2]; # perl array indices start from 0, not 1.
printf '%s<-read.csv("%s",header=TRUE)'."\n", $fn, "$pathx/$f";
# <snip> etc .etc. etc.
printf '%s_2y<-tail(%s_log,730)'."\n", $fn, $fn;
push @frames,"${fn}_2y";
}
print "df-<data.frame(", join(',',@frames), ")\n";
注:ディレクトリの再帰が必要な場合は、File::Find
単純モジュールの代わりにこのモジュールを使用できます。readdir()
出力例(ファイルa.csv
とb.csv
)c.csv
:
a<-read.csv("/path/to/my/files/a.csv",header=TRUE)
a_2y<-tail(a_log,730)
b<-read.csv("/path/to/my/files/b.csv",header=TRUE)
b_2y<-tail(b_log,730)
c<-read.csv("/path/to/my/files/c.csv",header=TRUE)
c_2y<-tail(c_log,730)
df-<data.frame(a_2y,b_2y,c_2y)
または次のようにawk
:
注:awkには関数がないため、join()
関数を作成する必要がありました。関数もawk
ないのでreaddir()
、最も簡単な方法は'sの出力をパイプfind
にパイプすることです(sh
必要に応じてこれを行うためのラッパースクリプトを書く)。
#!/usr/bin/awk -f
BEGIN {
FS="[./]";
delete A; # has side-effect of defining A as an array
};
# i isn't an argument to this function, it's a local variable.
# in awk, extra whitespace separates function args from declaration
# of local variable(s)
function join(array,sep, i) {
result=array[1]; # awk array indices start from 1
for (i=2;i<=length(array);i++) result = result sep array[i];
return result;
};
# main code block, run on every input line
{
fn=$(NF-1);
printf "%s<-read.csv(\"%s\",header=TRUE)\n", fn, $0;
# <snip> etc .etc. etc.
printf "%s_2y<-tail(%s_log,730)\n", fn, fn;
A[length(A)+1] = sprintf("%s_2y",fn);
};
END {
print "df-<data.frame(" join(",",A) ")";
}
たとえば、別の名前で保存してmyscript.awk
実行可能にし、次chmod
のように実行します。
find "${PATHX}" -maxdepth 1 -type f -name "*.csv" | ./myscript.awk
出力はバージョンと同じですperl
。
最後に、bashで同じアルゴリズムを使用します。
#!/bin/bash
PATHX="/path/to/my/files"
declare -a frames=()
# get list of .csv files and store in array csvfiles.
csvfiles=( $(find "$PATHX" -maxdepth 1 -type f -name '*.csv' ) )
function join() {
local sep result i
sep="$1" ; shift
result="$1" ; shift
for i in "$@" ; do result="$result$sep$i" ; done
printf '%s' "$result"
}
for f in "${csvfiles[@]}" ; do
fn=$(basename "$f" '.csv')
printf "%s<-read.csv(\"%s\",header=TRUE)\n" $fn $f;
# <snip> etc .etc. etc.
printf "%s_2y<-tail(%s_log,730)\n" $fn $fn;
frames+=( "${fn}_2y" )
done
echo 'df-<data.frame('$( join ',' "${frames[@]}" )')';
これはwhile read
、シェルスクリプトで一連の行を処理するためのほとんど常に最悪の方法であるループを防止します。ループの使用を避けるすべて - 配列の周りまたはawk
ループperl
を使用してください。sed
for
while read