web-dev-qa-db-ja.com

すべてのcsvファイルの最初の200行をbashを使用してディレクトリに保持するにはどうすればよいですか?

非常に大きなcsvファイルが約50個あり、数千行あります。

そして、私はそれらのそれぞれの最初の200行を保持したいだけです-生成されたファイルが元のファイルを上書きする場合は大丈夫です。

これを行うにはどのコマンドを使用すればよいですか?

12
AGamePlayer

現在のディレクトリにすべてのCSVファイルが含まれ、それらすべてに.csvファイル名サフィックスがあると仮定します。

for file in ./*.csv; do
    head -n 200 "$file" >"$file.200"
done

これにより、各CSVファイルの最初の200行が、headとリダイレクトを使用して新しいファイルに出力されます。新しいファイルの名前は古いファイルと同じですが、名前の最後に.200が追加されています。新しいファイル名が既に存在するかどうかを確認するチェックはありません。

オリジナルを置き換える場合:

for file in ./*.csv; do
    head -n 200 "$file" >"$file.200" &&
    mv "$file.200" "$file"
done

headコマンドの最後の&&は、mvの実行に問題があった場合にheadが実行されないようにします。

CSVファイルが現在のディレクトリの下のサブディレクトリに分散している場合は、shopt -s globstarを使用し、ループ内のパターン./*.csv./**/*.csvに置き換えます。これにより、現在のディレクトリ以下のCSVファイルが検索され、それぞれに対して操作が実行されます。 **グロビングパターンは、サブディレクトリに「再帰的に」一致しますが、globstar Shellオプションが設定されている場合のみです。


改行が埋め込まれたデータを含むCSVファイルの場合、レコードが切り捨てられる可能性があるため、上記は正しく機能しません。代わりに、CSV対応のツールを使用してジョブを実行する必要があります。

以下では、CSVファイルを解析および一般的に操作するためのコマンドラインツールのセットであるCSVkitと、JSONファイルを操作するためのツールであるjqを使用します。

CSVキットには、特定の時点でCSVファイルを切り捨てるツールはありませんが、CSVファイルをJSONに変換し、jqを使用して最初の200レコードのみを出力できます。

for file in ./*.csv; do
    csvjson -H "$file" | jq -r '.[:200][] | map(values) | @csv' >"$file.200" &&
    mv "$file.200" "$file"
done

以下の短い例のようなCSVファイルを考えると、

a,b,c
1,2,3
"hello, world",2 3,4
"hello
there","my good
man",Nice weather for ducks

csvjsonコマンドは

[
  {
    "a": "a",
    "b": "b",
    "c": "c"
  },
  {
    "a": "1",
    "b": "2",
    "c": "3"
  },
  {
    "a": "hello, world",
    "b": "2 3",
    "c": "4"
  },
  {
    "a": "hello\nthere",
    "b": "my good\nman",
    "c": "Nice weather for ducks"
  }
]

jqツールはこれを受け取り、配列内の各オブジェクト(最初の200オブジェクトに制限されています)に対して値を配列として抽出し、CSVとしてフォーマットします。

CSVkitの別のツールであるcsvpyを使用してこの変換を直接実行することはおそらく可能ですが、私のPythonスキルが存在しないため、解決策を思い付くことはしませんそれはそれを行います。

42
Kusalananda

以前の回答では、データをコピーしてファイルを上書きしていました。この手法では、同じiノードを保持し、コピーを行わず、全体的に高速で実行する必要があります。各ファイルについて:

(a)最初の200行を読み取って、各ファイルの長さを見つけます。

(b)GNU coreutilsからtruncateを使用するか、一部のBSDシステムで見つかったtruncateを使用して、ファイルをその長さに切り詰めます。

SZ="$( head -n 200 -- "${file}" | wc -c )"
truncate -s "${SZ}" -- "${file}"
23
Paul_Pedant

シェルグロビングでsedを使用する:

sed -ni '1,200p' *.csv

Globbing/sed/parallelの使用:

printf '%s\n' *.csv | parallel -- sed -ni '1,200p' {}

これはすべてを検索します.csvファイルを現在のディレクトリに入れ、GNU parallelにフィードします。これにより、sedコマンドが実行され、最初の200行のみが保持されます。これにより、所定の場所にファイル。

またはパラレルでヘッドを使用する:

printf '%s\n' *.csv | parallel -- head -n 200 {} ">" {}.out

これにより、.outサフィックス。

15
jesse_b

Ksh93とPOSIX準拠のhead実装(カーソルが出力する最後の行の直後のstdin内にカーソルを置くもの)では、次のことができます。

for file in ~(N)./*; do
  [ -f "$file" ] || continue # skip non-regular files
  head -n 200 0<>; "$file" > /dev/null
done

<>;リダイレクト演算子は、<>標準演算子のバリアントであり、リダイレクトされたコマンドが返された後、コマンドが成功の終了ステータスで戻る場合に、ファイルを所定の位置で切り捨てます。

ここでは、headの出力を破棄します。200行目の直後にカーソルを置いたままにしておくことに興味があります。

残念ながら、ksh93の組み込みheadbuiltin headを発行した場合、または/opt/ast/bin$PATH内のheadコマンドを含むディレクトリの前にある場合に有効)は、このインスタンスではPOSIXlyで動作しません。 (他のほとんどのhead実装と同様に)入力をチャンクで読み取りますが、seekingを呼び出して200行目の終わりに戻ることはありません。強制的にシークを実行させるには、最初に組み込みheadを使用する目的を無効にする外部コマンドを実行する必要があります。

builtin head # enable ksh93's head builtin
{ head -n 200 && /bin/true; } 0<>; file > /dev/null

外部ユーティリティの呼び出しを含まない別の有効なアプローチは、headが返された後に明示的な0オフセットシークを行うことです。

builtin head # enable ksh93's head builtin
for file in ~(N)./*; do
  [ -f "$file" ] || continue # skip non-regular files
  { head -n 200 && exec <#((CUR)); } 0<>; "$file" > /dev/null
done

特にCSV入力の場合、最初の200のCSVレコードを保持するには(CSVレコードに複数の行が含まれる可能性があるため("..."引用フィールドに埋め込まれるため)、ループでCSVを読み取るように特別に設計されたksh93read -Sを使用できます。

for file in ~(N)./*.csv; do
  [ -f "$file" ] || continue # skip non-regular files
  for ((i=0;i<200;i++)); do 
    IFS=, read -rSA discard
  done 0<>; "$file"
done
3

私は比較的新しいので、優しくしてください。私が提案しているソリューションが最適でない場合は、建設的なフィードバックをいただければ幸いです。

1から4までの番号が付けられた4つのサンプルファイルを作成しました。 touch {1..4}および各ファイルには、最初のファイルなどの10個のサンプル行と、次のファイルの11〜20行目などが含まれています。

ファイル1

Line 1
Line 2
Line 3
Line 4
Line 5
Line 6
Line 7
Line 8
Line 9
Line 10 

ファイル2

Line 11
Line 12
Line 13
Line 14
Line 15
Line 16
Line 17
Line 18
Line 19
Line 20

例として最初の2行を抽出するには(200に外挿できます)、コマンドhead -n 2 {1..4}は出力を返します。

==> 1 <==
Line 1
Line 2

==> 2 <==
Line 11
Line 12

==> 3 <==
Line 21
Line 22

==> 4 <==
Line 31
Line 32

このコマンドは、head -n 2 {1..4} > ExtractedOutputコマンドを使用して、出力を別のファイルにリダイレクトできます。

1
Ryan

edを使用して、各ファイルを切り捨てます。

for f in *.csv; do
  printf '201,$d\nwq\n' | ed "$f"
done

バックアップを保存したい場合は、代わりにexを使用する方が簡単な場合があります。 (exの方が使いやすいと考えるかもしれませんが、単にw!%.bak|は、最初にバックアップの作成をスキップします。)

for f in *.csv; do
    ex -c 'w!%.bak|201,$d|wq' "$f"
done
1
chepner