スペースを含むファイルをコピーするには、head からの出力を使用します。

スペースを含むファイルをコピーするには、head からの出力を使用します。

フォルダー内に名前にスペースを含むファイルが 11 個あり、最新の 10 個をコピーしたいと考えていますls -t | head -n 10。以前は最新の 10 個のファイルのみを取得していました。cp ステートメントで式を使用しようとすると、名前にスペースがあるためファイルが見つからないというエラーが発生します。

例:cp: cannot stat ‘10’: No such file or directoryファイルの名前が10 abc.pdf

cp ステートメント:cp $(ls -t | head -n 10) ~/...

これを動作させるにはどうすればよいですか?

答え1

Bash を使用していて、100% 安全な方法が必要な場合 (ファイル名を真剣に処理する必要があることを苦労して学んだので、そうしたいのだと思います)、次の方法を試してください。

shopt -s nullglob
while IFS= read -r file; do
    file=${file#* }
    eval "file=$file"
    cp "$file" Destination
done < <(
    for f in *; do
        printf '%s %q\n' "$(date -r "$f" +'%s')" "$f"
    done | sort -rn | head -n10
)

いくつかの部分を見てみましょう:

for f in *; do
    printf '%s %q\n' "$(date -r "$f" +'%s')" "$f"
done

これは、次の形式で標準出力に出力されます。

Timestamp Filename

ここで、timestampはEpochからの秒数で表した変更日(-rのオプションで取得date)であり、Filenameはファイル名の引用符で囲まれたバージョンです。シェル入力として再利用できる(help printfおよび%q書式指定子を参照してください)。これらの行は、 を使用して (数値の逆順に) ソートされsort、最初の 10 行だけが保持されます。

次に、これがwhileループに送られます。代入によってタイムスタンプが削除されfile=${file# *}(これにより、最初のスペースまですべてが削除されます)、次に、明らかに危険な行eval "file=$file"から によって導入されたエスケープ文字が削除されprintf %q、最後にファイルを安全にコピーできるようになります。

おそらく最善のアプローチや実装ではありませんが、あらゆるファイル名に関して 100% 安全であることが保証され、目的を達成できます。ただし、通常のファイル、ディレクトリなどはすべて同じように扱われます。通常のファイルに制限する場合は、行[[ -f $f ]] || continueの直後に を追加しますfor f in *; do。また、隠しファイルは考慮されません。隠しファイルが必要な場合 (もちろん や は除く.)..は、 を追加しますshopt -s dotglob


もう 1 つの 100% Bash ソリューションは、Bash を直接使用してファイルをソートすることです。クイックソートを使用するアプローチを次に示します。

quicksort() {
    # quicksorts the filenames passed as positional parameters
    # wrt modification time, newest first
    # return array is quicksort_ret
    if (($#==0)); then
        quicksort_ret=()
        return
    fi
    local pivot=$1 oldest=() newest=() f
    shift
    for f; do
        if [[ $f -nt $pivot ]]; then
            newest+=( "$f" )
        else
            oldest+=( "$f" )
        fi
    done
    quicksort "${oldest[@]}"
    oldest=( "${quicksort_ret[@]}" )
    quicksort "${newest[@]}"
    quicksort_ret+=( "$pivot" "${oldest[@]}" )
}

次に、それらを整理し、最初の 10 個を保存して、宛先にコピーします。

$ shopt -s nullglob
$ quicksort *
$ cp -- "${quicksort_ret[@]:0:10}" Destination

前の方法と同じように、通常のファイル、ディレクトリなどはすべて同じように扱われ、隠しファイルはスキップされます。


別の方法として、および引数lsがある場合は、次のようなものを使用できます。iq

ls -tiq | head -n10 | cut -d ' ' -f1 | xargs -I X find -inum X -exec cp {} Destination \; -quit

これにより、ファイルの inode が表示され、findinode によって参照されるファイルに対してコマンドを実行できます。

ls同様に、通常のファイルだけでなく、ディレクトリなども処理します。これは の出力形式に大きく依存しているため、あまり好きではありません...

答え2

ディレクトリ内のファイルのグループについては、常に最も古いものが除外されます。

less_oldest() {
    while [ -e "$1" ] ; do
        for f do [ "$f" -ot "$1" ] && break
        done && { set -- "$@" "$1"
            shift ; continue
        } ; shift ; break
    done
    printf '///%s///\n' "$@" |
        sed "s|'"'|&"&"&|g;'"s|///|'|g"
}

これは完全に移植可能で、返される文字に関係なく、安全にシェルで引用されたファイル名の配列を出力します。あなたの場合、必要なのは dro pone だけなので、次のように 1 回だけ実行する必要があります。

{   printf 'cp -t ~"/..."'
    less_oldest "${dir_of_11_files}/"*
} | sh

関連情報