現場でこういう作業は珍しくありません。バックアップ対象のパス一覧、移行対象ファイルのリスト、ログ整理の候補ファイル一覧……テキストファイルに書き出されたフルパスを読み込んで一括処理したい場面は意外と多いものです。
この記事では、while readループを使ってパス一覧から各ファイルを別フォルダへ一括移動する方法を実践的に解説します。
基本形から始まり、移動先フォルダの自動作成・コピー版・エラースキップまで、現場で使える応用例も網羅します。
この記事のポイント
・while read line; do mv "$line" 移動先; done < ファイル一覧が基本形
・IFS= read -r line で行頭・行末スペースとバックスラッシュを安全に扱える
・mkdir -p "$(dirname ...)" でフォルダ自動作成しながら移動できる
・移動失敗時は || echo で続行させてログに残すのが現場の鉄則
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
なぜwhile readループを使うのか
パス一覧ファイルを読んで一括処理する方法はいくつかあります。
代表的なのは以下の3つです。
・xargs mv:シンプルだがスペースを含むパスに弱い(-d '\n' 指定が必要)・for ループ:ファイル名のグロブ展開と競合するケースがある
・while read ループ:1行ずつ確実に読み込め、スペース・記号を含むパスも安全に処理できる
特に「フルパスが書かれたテキストファイル」を入力にする場合、while readは最も扱いやすい選択肢です。変数に1行ずつ格納するため、mvの引数にダブルクォートで渡せばスペース入りのパスでも崩れません。
実行環境
RHEL 9.4 / Rocky Linux 9.4 / Ubuntu 24.04 LTS で動作確認済みです。bashスクリプト(bash 5.x系)を前提にします。
基本形:while readでパス一覧を読み込んでmvする
1. パス一覧ファイルを用意する
まず移動対象ファイルのフルパスを1行1ファイルで記述したテキストファイルを用意します。
# 移動対象のパス一覧(filelist.txt の例) /var/log/app/2024/01/access.log /var/log/app/2024/01/error.log /var/log/app/2024/02/access.log /var/log/app/2024/02/error.log
このファイルを /tmp/filelist.txt として保存してある前提で進めます。
2. 移動先フォルダを作成する
mkdir -p /backup/archive
3. while readの基本形で一括移動する
while IFS= read -r line; do mv "$line" /backup/archive/ done < /tmp/filelist.txt
IFS=(IFSを空に設定)と -r(バックスラッシュをエスケープ文字と解釈しない)の組み合わせが重要です。これを省略すると、行頭・行末のスペースが除去されたり、パス中の \n が改行として解釈されたりして思わぬトラブルになります。
4. 実行結果を確認する
実際のサーバーで実行した出力例です(IPアドレス・ホスト名はマスク済み)。
[admin@sv-xx-xxx ~]$ while IFS= read -r line; do mv "$line" /backup/archive/; done < /tmp/filelist.txt [admin@sv-xx-xxx ~]$ ls /backup/archive/ access.log error.log [admin@sv-xx-xxx ~]$ echo "移動完了" 移動完了
ただしこの基本形は、複数ディレクトリから集めたファイルが同名の場合に後から来たファイルで上書きされます。次のセクションでサブディレクトリ構造を維持する方法を紹介します。
応用・実務Tips
1. サブディレクトリ構造ごと移動先に再現する
ログをアーカイブする際、元のディレクトリ構造をそのまま維持したいことがあります。dirnameとmkdir -pを組み合わせると、移動先にサブディレクトリを自動作成しながら移動できます。
DEST=/backup/archive while IFS= read -r line; do # 移動先に同じディレクトリ構造を作成 dest_dir="${DEST}$(dirname "$line")" mkdir -p "$dest_dir" mv "$line" "$dest_dir/" done < /tmp/filelist.txt
実行後の確認:
[admin@sv-xx-xxx ~]$ find /backup/archive -type f /backup/archive/var/log/app/2024/01/access.log /backup/archive/var/log/app/2024/01/error.log /backup/archive/var/log/app/2024/02/access.log /backup/archive/var/log/app/2024/02/error.log
元の /var/log/app/2024/01/ 構造がそのまま再現されました。
2. 移動の前に件数を確認するドライラン
大量のファイルを一括移動する前に、件数を把握してから実行するのが安全です。
# 件数確認 wc -l /tmp/filelist.txt # 存在確認(存在しないパスをリストアップ) while IFS= read -r line; do [ ! -f "$line" ] && echo "存在しない: $line" done < /tmp/filelist.txt
3. エラーをスキップして続行し、失敗ファイルだけログに残す
「1ファイルだけ権限エラーが出ても残りは続けてほしい」という現場の要望は多いです。||(OR演算子)でmvが失敗した場合の処理を追加します。
ERROR_LOG=/tmp/move_errors.log > "$ERROR_LOG" # ログ初期化 while IFS= read -r line; do mv "$line" /backup/archive/ || echo "FAILED: $line" >> "$ERROR_LOG" done < /tmp/filelist.txt echo "処理完了。失敗件数: $(wc -l < "$ERROR_LOG")" cat "$ERROR_LOG"
ただし、echo "FAILED: $line" >> "$ERROR_LOG" のリダイレクトはシェルスクリプト内では半角で書きます。ここでは可読性のため全角表記で示しています。
4. mvではなくcpで「コピー」版にする
元ファイルを消さずにコピーだけしたい場合は mv を cp に変えるだけです。
while IFS= read -r line; do cp "$line" /backup/archive/ done < /tmp/filelist.txt
ディレクトリごとコピーする場合は cp -a で属性・タイムスタンプを保持したままコピーできます。
5. findで対象ファイルを動的に生成してwhileに渡す
「リスト作成とループ処理を1行でやりたい」場合はプロセス置換(<())を使います。
# /var/log/app 配下の2024年1月のログを別フォルダへ一括移動 while IFS= read -r line; do mv "$line" /backup/archive/ done < <(find /var/log/app/2024/01 -type f -name "*.log")
ただし <() はbash固有の機能(sh では使えない)です。スクリプト先頭の #!/bin/bash を必ず確認してください。
6. 処理済みファイルに移動済みフラグを付けながら進捗管理する
DONE_LOG=/tmp/moved_done.log > "$DONE_LOG" while IFS= read -r line; do if mv "$line" /backup/archive/; then echo "OK: $line" >> "$DONE_LOG" else echo "NG: $line" >> "$DONE_LOG" fi done < /tmp/filelist.txt echo "--- 結果 ---" grep -c "^OK" "$DONE_LOG" grep -c "^NG" "$DONE_LOG"
トラブルシュート・エラー対処
「No such file or directory」が出る
パス一覧ファイルに改行コードが \r\n(Windows形式)で含まれていると、行末に \r(キャリッジリターン)が残り、ファイルが見つかりません。
# 改行コード確認 file /tmp/filelist.txt # → "CRLF line terminators" と出ればWindows形式 # CRLFをLFに変換 sed -i 's/\r//' /tmp/filelist.txt # または dos2unix /tmp/filelist.txt
「mv: cannot move ... Permission denied」が出る
移動元ファイルの読み取り権限、または移動先ディレクトリへの書き込み権限がない場合に発生します。
# 移動元ファイルの権限確認 ls -la /var/log/app/2024/01/access.log # 移動先ディレクトリの権限確認 ls -ld /backup/archive/ # 必要に応じてchmodで書き込み権限を追加 chmod 755 /backup/archive/
権限変更の詳細はLinux 基本コマンドの解説を参照してください。
スペースを含むパスで移動が失敗する
IFS= read -r を使っていても、mv $line(ダブルクォートなし)では単語分割が起きます。必ず mv "$line" と書いてください。
# NG:スペース含みパスで失敗する while IFS= read -r line; do mv $line /backup/archive/ # ← NG: クォートなし done < /tmp/filelist.txt # OK:ダブルクォートで囲む while IFS= read -r line; do mv "$line" /backup/archive/ # ← OK done < /tmp/filelist.txt
パス一覧ファイルに空行が混入している
空行があると mv "" /backup/archive/ が実行され、カレントディレクトリを移動先にコピーしようとするエラーが出ます。
# 空行をスキップする書き方 while IFS= read -r line; do [ -z "$line" ] && continue mv "$line" /backup/archive/ done < /tmp/filelist.txt
本記事のまとめ
while readループを使ったファイル一括移動の要点を整理します。
| やりたいこと | コマンド・書き方 |
|---|---|
| パス一覧から一括移動(基本形) | while IFS= read -r line; do mv "$line" /dest/; done < filelist.txt |
| ディレクトリ構造を維持して移動 | mkdir -p "${DEST}$(dirname "$line")" && mv "$line" ... |
| エラーをスキップして続行 | mv "$line" /dest/ || echo "FAILED: $line" >> error.log |
| コピー版(元ファイル保持) | cp "$line" /dest/ |
| findと組み合わせてリスト不要で処理 | while IFS= read -r line; do ...; done < <(find ...) |
| 空行スキップ | [ -z "$line" ] && continue |
| CRLF改行コードを修正 | sed -i 's/\r//' filelist.txt |
while readループのより基本的な使い方(CSVや設定ファイルの1行処理など)はLinux 基本コマンドの解説も参照してください。
ファイルの移動・コピー前後で権限が変わっていないか確認するには、ls コマンドの基本オプションで ls -la を確認する習慣をつけておくと障害時の切り分けがスムーズです。
while readの使い方を覚えたら、シェルスクリプト全体の基礎を体系的に固めませんか?
ループ処理や変数の扱い方は、手順を知っているだけでは不十分です。なぜそう書くのかという背景まで理解してこそ、現場で応用が利く力がつきます。
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、『Linuxサーバー構築入門マニュアル(図解60P)』を完全無料でプレゼントしています。
「独学の時間がもったいない」「プロから直接、現場の技術を最短で学びたい」という本気の方には、2日で実務レベルのスキルが身につく【初心者向けハンズオンセミナー】も開催しています。
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
登録10秒/合わなければ解除3秒 / 詳細はこちら
- 次のページへ:Linuxでファイル一覧を作業証跡として残す方法|tree・lsの出力をテキスト保存する手順
- 前のページへ:Linuxでcronが実行されない時の調査手順|ログ・環境変数・権限の3点を切り分ける
- この記事の属するカテゴリ:Linuxtips・シェルスクリプトへ戻る

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は1分、解除もいつでも可。
登録無料・いつでも解除できます