いろいろ備忘録日記

主に .NET とか Go とか Python絡みのメモを公開しています。

データを任意のサイズに分割 (bash)

概要

小ネタ。知ってるともしかしたらイザというときに便利かもしれません。

今はネットワークもストレージも大容量になったので多少大きなファイルを送受信してもあまり困ることはないかもしれません。

一昔前は、回線がとても細い現場とかが多かった記憶。

そういうときに、例えば調査用にログファイルを実機から採取したいって思っても、ファイルサイズがとても大きいクソログファイルとかがあったりするとちょっと困ります。

そんなときに以下のように分割してしまって、必要な分のみを取得したりすると時間も節約できたりするかもしれません。

Linuxでのデータファイルの分割には主に dd コマンドくんが活躍してくれます。

コツは、分割する際に dd コマンドに対して

  • conv=noerror を指定する
  • skipオプションを指定する

ですね。分割サイズによっては最後の分割ファイルは途中でデータが終わってしまう可能性があります。

その場合でもエラーにせずにデータを最後まで書ききってもらうのに conv=noerror を指定します。

サンプル

#!/usr/bin/env bash

#
# ddコマンドを使ってデータを任意のサイズに分割
#
# REFERENCES:
#   - https://www.gnu.org/software/gawk/manual/gawk.html
#   - https://www.gnu.org/software/gawk/manual/gawk.html#Printf-Examples
#   - https://www.gnu.org/software/coreutils/manual/html_node/sort-invocation.html
#   - https://www.gnu.org/software/coreutils/manual/html_node/dd-invocation.html#dd-invocation
#
basedirpath=/tmp/try-linux/split/using_dd

rm -rf "${basedirpath}"
mkdir -p "${basedirpath}"

## テスト用のファイルを生成
#
# 500KBのファイル
dd if=/dev/zero of="${basedirpath}/zero.bin" bs=1024 count=500 2>/dev/null

## 生成したファイルのサイズを保持(分割時に利用する)
#
_size=$(ls -l "${basedirpath}/zero.bin" | awk '{print $5}')
echo "FileSize: ${_size}"

## 30KBずつで分割
#
_srcfile="${basedirpath}/zero.bin"
_chunk=$(( 1024 * 30 ))
_total=0
_count=0

while [ "${_total}" -lt "${_size}" ]
do
    (( _count++ ))

    # きっちり分割できない場合でもエラーにならずに入力ファイルの最後まで
    # 書き出してもらうために conv=noerror オプションを付与
    #
    # また、skip オプションを指定しているので、必要なメモリ容量は一定となるようにしている
    dd \
        conv=noerror \
        if="${_srcfile}" \
        of="${_srcfile}.${_count}" \
        bs="${_chunk}" \
        skip="$(( _count - 1 ))" \
        count=1 \
        2>/dev/null

    _total=$(( _total + _chunk ))
done

echo "Split: ${_count} files"

## 確認
#
# - ls -l の先頭行は total xxxx という表示になるので除外 (awk '{if (NR>1) printf "%6d %s\n",$5,$9}')
# - 分割数が10個以上になると、そのままだと数字順に並ばないのでソート (sort -n -t . -k 3)
#   - -n : --numeric-sort (数値でソート)
#   - -t : --field-separator (指定した文字をセパレータとして利用)
#   - -k : --key (ソート対象とするカラム位置)
#
ls -l "${basedirpath}" \
    | awk '{if (NR>1) printf "%6d %s\n",$5,$9}' \
    | sort -n -t . -k 3

echo '----------------------------------------------------'

## 復元
#
cat $(ls ${basedirpath}/zero.bin.* | sort -n -t . -k 3) > "${basedirpath}/zero.bin.restore"
ls -l "${basedirpath}"/{zero.bin,zero.bin.restore}

echo '----------------------------------------------------'

## 復元確認
#
diff -s "${basedirpath}"/{zero.bin,zero.bin.restore}

try-linux/using_dd.sh at master · devlights/try-linux · GitHub

実行すると以下のようになります。

gitpod /workspace/try-linux $ make
ENTER EXAMPLE NAME: split_u

[INPUT ] split_u
[TARGET] split_using_dd
[SCRIPT] basic/split/using_dd.sh

===== START [basic/split/using_dd.sh] =====
FileSize: 512000
Split: 17 files
512000 zero.bin
 30720 zero.bin.1
 30720 zero.bin.2
 30720 zero.bin.3
 30720 zero.bin.4
 30720 zero.bin.5
 30720 zero.bin.6
 30720 zero.bin.7
 30720 zero.bin.8
 30720 zero.bin.9
 30720 zero.bin.10
 30720 zero.bin.11
 30720 zero.bin.12
 30720 zero.bin.13
 30720 zero.bin.14
 30720 zero.bin.15
 30720 zero.bin.16
 20480 zero.bin.17
----------------------------------------------------
-rw-r--r-- 1 gitpod gitpod 512000 Feb  1 08:51 /tmp/try-linux/split/using_dd/zero.bin
-rw-r--r-- 1 gitpod gitpod 512000 Feb  1 08:51 /tmp/try-linux/split/using_dd/zero.bin.restore
----------------------------------------------------
Files /tmp/try-linux/split/using_dd/zero.bin and /tmp/try-linux/split/using_dd/zero.bin.restore are identical
===== END   [basic/split/using_dd.sh] =====

DONE

参考情報

www.gnu.org

www.gnu.org

www.gnu.org


過去の記事については、以下のページからご参照下さい。

  • いろいろ備忘録日記まとめ

devlights.github.io

サンプルコードは、以下の場所で公開しています。

  • いろいろ備忘録日記サンプルソース置き場

github.com

github.com

github.com