フォルダー内に名前にスペースを含むファイルが 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
がある場合は、次のようなものを使用できます。i
q
ls -tiq | head -n10 | cut -d ' ' -f1 | xargs -I X find -inum X -exec cp {} Destination \; -quit
これにより、ファイルの inode が表示され、find
inode によって参照されるファイルに対してコマンドを実行できます。
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