web-dev-qa-db-ja.com

ファイルを一覧表示する再帰シェルスクリプト

ディレクトリ(およびサブディレクトリ)内の特定の種類のファイルを一覧表示するシェルスクリプトを記述しようとしています。再帰的な部分で苦労しています。

ここに私が持っているものがあります:

#!/bin/sh

#download dir
DOWNLOADING_DIR=/Users/richard/Downloads

echo "Starting Script..."

for FILE in $DOWNLOADING_DIR/*
do
    if [ -d "$FILE" ]
    then
        echo "...Checking Directory "$FILE
        for DFILE in $FILE/*
        do
            echo "Found file ... $DFILE"
        done
    else
        echo "Found file ... $FILE"
        echo ""
    fi
done

問題は、ディレクトリが見つかったときに、ディレクトリ内のファイルの名前が見つからないことです。サブディレクトリ内のファイルではなく、サブディレクトリ名のみがリストされます。最初のディレクトリのファイルに対して機能します。

.txtまたは.docファイルを検索して別のディレクトリに移動するには、このスクリプトが必要です。

3
Borg357

スクリプトはそれ自身を呼び出さないため、not再帰的です。

以下は、再帰的に持っているようなものを実装するバリエーションです。

_#!/bin/bash

walk_dir () {
    shopt -s nullglob dotglob

    for pathname in "$1"/*; do
        if [ -d "$pathname" ]; then
            walk_dir "$pathname"
        else
            printf '%s\n' "$pathname"
        fi
    done
}

DOWNLOADING_DIR=/Users/richard/Downloads

walk_dir "$DOWNLOADING_DIR"
_

関数_walk_dir_は、唯一の引数としてディレクトリパス名を受け取り、その内容を反復処理します。ディレクトリが見つかると、それ自体を再帰的に呼び出して、そのサブディレクトリを走査します。

これを変更して、ファイル名のサフィックスが_.txt_または_.doc_であるファイルを検索します。

_#!/bin/bash

walk_dir () {    
    shopt -s nullglob dotglob

    for pathname in "$1"/*; do
        if [ -d "$pathname" ]; then
            walk_dir "$pathname"
        else
            case "$pathname" in
                *.txt|*.doc)
                    printf '%s\n' "$pathname"
            esac
        fi
    done
}

DOWNLOADING_DIR=/Users/richard/Downloads

walk_dir "$DOWNLOADING_DIR"
_

上記の「ファイル」とは、ディレクトリまたはディレクトリへのシンボリックリンクではないすべてのものを意味することに注意してください。これは通常のファイルとは異なる場合があります。 dotglobおよびnullglobシェルオプションをbashに設定することで、隠しパス名を見つけることができ、空の可能性があるディレクトリを特別にテストする必要がなくなります。

隠された名前を気にしない_/bin/sh_のバリエーション:

_#!/bin/sh

walk_dir () {
    for pathname in "$1"/*; do
        if [ -d "$pathname" ]; then
            walk_dir "$pathname"
        Elif [ -e "$pathname" ]; then
            case "$pathname" in
                *.txt|*.doc)
                    printf '%s\n' "$pathname"
            esac
        fi
    done
}

DOWNLOADING_DIR=/Users/richard/Downloads

walk_dir "$DOWNLOADING_DIR"
_

globstarexglobおよびbashシェルオプションを使用すると、ファイルを移動するために次の(再帰なしで)こともできます。

_shopt -s globstar extglob

mv "$DOWNLOADING_DIR"/**/*.@(txt|doc) "$destdir"
_

...結果のファイルリストが長すぎることが判明しない限り。 _**_は、パス名のスラッシュ全体で一致し(globstarによって有効化)、*.@(txt|doc)は、_.txt_または_.doc_で終わるファイル名と一致します(有効化によってextglob)。


_.txt_または_.doc_のファイル名サフィックスが付いた通常のファイルを最上位のディレクトリ_$topdir_の中または下に見つけ、それらを他のディレクトリに移動するはるかに効率的で移植可能な方法_$destdir_:

_find "$topdir" -type f \( -name '*.txt' -o -name '*.doc' \) \
    -exec mv {} "$destdir" \;
_

GNU mvを使用すると、もう少し効率的にすることができます。

_find "$topdir" -type f \( -name '*.txt' -o -name '*.doc' \) \
    -exec mv -t "$destdir" {} +
_

このバリエーションでは、一度に1つのファイルではなくバッチのファイルが移動します。 mvを_-v_とともに使用して、何が移動するかを確認するか、_-print_の前に_-exec_を追加して、mvが呼び出されるパス名のリストを取得します。

12
Kusalananda