터치 명령의 폴더 이름을 이스케이프하는 방법은 무엇입니까?

터치 명령의 폴더 이름을 이스케이프하는 방법은 무엇입니까?

두 개의 스크립트를 만들었습니다. 먼저 모든 폴더의 mtime 타임스탬프를 읽고 time_old.txt에 한 줄씩 씁니다.

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

두 번째는 행을 다시 읽고 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하고 쉘 언어와 호환되는 형식으로 파일 경로를 인용하는 독립 실행형 유틸리티가 필요합니다 . 이것이 보장되지 않으면 위험해질 수 있습니다. 이는 또한 디렉터리당 하나의 프로세스를 분기하고 실행할 때 효율성을 감소시킵니다( 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)

내 실수는 어디에 있습니까?

파일에서 파일 이름을 인용하도록 배열했지만 문제는 인용문이 쉘 명령줄에 직접 있는 경우에만 작동한다는 것입니다. 확장 결과는 다음과 같습니다.아니요따옴표, 백슬래시 이스케이프 또는 기타 셸 구문을 구문 분석합니다. 대신 이러한 문자는 문자 그대로 처리됩니다. 따라서 이면 $line, , , 로 1650819409 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"

관련 정보