タッチコマンドのフォルダ名をエスケープする方法は?

タッチコマンドのフォルダ名をエスケープする方法は?

2つのスクリプトを作成しました。まず、すべてのフォルダのmtimeタイムスタンプを読み取り、time_old.txtに1行ずつ書き込みます。

for d in */ ; do
    time_old=$(stat --format %Y "$d")
    escaped=$(printf %q "$d")
    echo "$time_old $escaped" >> time_old.txt
done

2番目は行を再読み込みし、touchフォルダの古いタイムスタンプを使用して実行されます。

input="time_old.txt"
while IFS= read -r line
do
  echo "$line"
  touch_output=$(touch -m -d @$line)
  echo "$touch_output"
done < "$input"

これは、同様のフォルダには機能しますFolder1が、名前が 。Folder2インフォルダには機能しません。Foo & Bar

私の間違いはどこにありますか?

答え1

代わりに、次のことを行います(GNUツールの前提)。

find . ! -name . -prune -type d -printf '@%T@\0%p\0' > time_old.list

復元:

xargs -t -a time_old.list -r0 -n2 touch -md

を使用する場合は、printf %q結果をシェルコードとして解釈する必要があります。それは次のとおりです。

find . ! -name . -prune -type d -printf 'touch -md @%T@ ' \
  -exec printf '%q\n' {} \; > time_old.bash

そして:

bash -v ./time_old.bash

また覆います。

printfただし、この非標準をサポートし、%qシェル言語と互換性のある形式でファイルパスを引用するスタンドアロンユーティリティが必要です。これが保証されていないと危険になる可能性があります。これにより、ディレクトリごとに1つのプロセスを分岐して実行すると効率が低下します(statGNUアプローチと同様)。回避できる場合は、シェルが外部入力から生成されたコードを解釈することはできません。

また、touch標準出力には何も書きませんのでoutput=$(touch...)意味がありません。

zshここでは、安全な引用演算子(単一引用符を使用すると、ファイル名に含まれる文字や文字以外のファイル名に関係なく安全でなければならず、shに似たシェル(yashを除く)と互換性がなければなりません)をstat使用することもできます。実際にはGNUよりも前ののstatで、GNUismに頼る必要はありません。

zmodload zsh/stat

{
  for d (*(ND/)) 
    stat -A t -F @%s.%9. +mtime -- "$d" &&
    print -r touch -d $t -- ${(qq)d}
} > time_old.sh

そして回復を使用してくださいsh -v ./time_old.sh

答え2

escaped=$(printf %q "$d")
touch_output=$(touch -m -d @$line)

私の間違いはどこにありますか?

ファイルからファイル名を引用するように配置しましたが、問題は、引用符がシェルコマンドラインに直接ある場合にのみ機能することです。拡張結果は次のとおりです。いいえ引用符、バックスラッシュエスケープ、またはその他のシェル構文を解析します。代わりに、これらの文字は文字通り処理されます。したがって、これは、、、、$line1650819409 Foo\ \&\ Bar分割されます。これは次の質問に似ています。1650819409Foo\\&\Bar変数に保存されたコマンドをどのように実行できますか?evalシェルを使用して拡張値を解析できますが、これには特に信頼できないデータ修正などの独自の問題があります。)


ファイル名に改行文字を含めることができないと思われる場合は、直接保存できます。これにより、1650819409 Foo & Bar最初のスペースの後のすべてのエントリがファイル名になるような同様の行が生成されます。

> time_old.txt
for d in */ ; do
    case "$d" in
        *$'\n'*) printf >&2 "newlines in filenames not supported, skipping %q\n" "$d"; continue;;
    esac
    time_old=$(stat --format %Y -- "$d") &&
      printf '%s\n' "$time_old $d"
done > time_old.txt

input="time_old.txt"
while IFS= read -r line
do
  time=${line%% *}      # remove from first newline to end
  file=${line#* }       # remove up to and incl. the first newline
  printf >&2 "Changing time on '%s' to '%s'\n" "$file" "$time"
  touch -m -d "@$time" -- "$file"
done < "$input"

関連情報