![파일 이름 제거 및 이름 바꾸기](https://linux55.com/image/59089/%ED%8C%8C%EC%9D%BC%20%EC%9D%B4%EB%A6%84%20%EC%A0%9C%EA%B1%B0%20%EB%B0%8F%20%EC%9D%B4%EB%A6%84%20%EB%B0%94%EA%BE%B8%EA%B8%B0.png)
디렉토리에서 현재 파일 이름을 제거하고 영숫자 파일 이름으로 바꾸려면 어떤 이름 바꾸기 명령을 사용할 수 있습니까?
예를 들어 gg0001
,,, gg0002
잠깐? 나는 이 명령이 모든 유형의 확장자를 가진 파일을 처리할 수 있기를 원합니다. 그 확장을 유지하고 싶습니다.
rename
나는 Perl을 사용하는 것을 선호합니다.
이 명령을 시도했지만 파일 이름 앞에 "gg"가 추가되는 반면 원래 파일 이름을 바꾸고 싶습니다.
rename 's/^/gg_/' *
답변1
실제로는 명령이 필요하지 않으며 rename
셸에서 직접 수행할 수 있습니다.
c=0; for i in *; do let c++; mv "$i" "gg_$(printf "%03d" $c)${i#"${i%.*}"}"; done
printf "%03d" $c
변수는 인쇄되며 필요 $c
에 따라 앞에 0이 3개 추가됩니다. let c++
카운터 증가() $c
. 확장자를 추출합니다 ${i#"${i%.*}"}
. 관련 콘텐츠 더보기여기.
나는 이것을 사용하지 않을 것이다 rename
. 계산할 수 없을 것 같아요. 유효한 Perl 생성자가 s/.*/$c++/e
실패하고 계산을 수행하도록 하는 다른 방법이 없습니다. 이는 여전히 루프에서 실행하고 다른 것이 카운터를 증가시키도록 해야 함을 의미합니다. 그것은 다음과 같습니다:
c=0;for i in *; do let c++; rename -n 's/.*\.(.*)/'$c'.$1/' "$i"; done
그러나 더 간단한 방법을 사용하는 것보다 이점이 없습니다 mv
.
답변2
파일 이름에 작은따옴표가 없으면 '
다음을 수행할 수 있습니다.
i=0; set --
for f in *
do set -- "$@" "$f" gg "$((i+=1))" "${f#"${f%.*}"}"
done
printf "mv '%s' '%s%03d%s'\n" "$@" | sh
이렇게 하면 파일 이름에 a가 포함되어 있으면 .
해당 파일과 그 뒤의 모든 내용이 보존됩니다. 그렇지 않으면 필드가 비어 있고 printf
아무것도 인쇄되지 않습니다.
제 생각엔 있어도예파일 이름에 작은따옴표를 사용하면 됩니다...
i=0; set --
for f in *
do set -- "$@" "$((i+=1))" gg "$i" "$i#\"\${$i%.*}\""
done
printf 'mv "${%d}" "%s%03d${%s}"\n' "$@" | sh -s -- *
...아마도 전반적으로 더 안전할 것입니다.
그런데 생각해보니 둘 다 너무 힘들었다는 생각이 들기 시작했어요. 두 목록 중 하나를 파이프하는 유일한 이유는 0 패딩을 확인하지 않기 위해서입니다. 나는 서브셸에 목록을 만들고 write()
필요한 경우 한 서브셸에서 다른 서브셸로 스트리밍하는 것을 선호합니다.
하지만 제로 패딩은 그리 어렵지 않을 것입니다. 나는 약간의 조사를 한 결과 다음과 같은 작은 수학적 설명을 생각해 냈습니다.
$((z/=(m*=((i+=1)<$m?1:10))/$m))
반복 루프에 배치하면 각 항목을 한 번 증가시키고, 각 요소 10에 대해 지수 폴백을 사용하여 승수를 전진시키고 $i
, 동일한 항목 각각에 대해 십일조를 부과합니다. 아이디어는 루프를 시작하기 전에 가능한 가장 높은 증분을 유지하는 값을 얻는 것입니다. 이것은 매우 간단합니다.$m
$z
$z
$i
set -- *; max=$# z=1
until [ "${#z}" -gt "${#max}" ]; do : "$((z*=10))"; done
이것은 완전히 자동화된 방식입니다. 가능한 가장 높은 숫자까지 자동으로 채워집니다. 하지만 솔직히 말해서 $z
1로 시작하고 그 뒤에 0만 오는 숫자이면 됩니다.
set -- *; z=1000
...수천 개의 필드가 채워집니다. 어쨌든 테스트하는 동안 사용한 몇 가지 데모는 다음과 같습니다.
time dash <<\CMD
str=100000 z=1 m=1 i=0
until [ "${#z}" -gt "${#str}" ]; do : "$((z*=10))"; done
while : "$((z/=(m*=((i+=1)<$m?1:10))/$m))" &&
case "$i" in (*[2-8]*|*9*[10]*|*1*[19]*) continue;;
([019]*) set -- "$@" "z: $z m: $m i: $i" "${z#?}$i";;esac
do [ "$i" = "$str" ] && printf %s\\n "$@" && break; done
CMD
많은 수의 항목을 계속 처리할 수 있는지 확인하고 싶지만 100,000개의 결과를 읽고 싶지 않기 때문에 대부분은 필터링된 출력입니다.
산출
z: 100000 m: 10 i: 1
000001
z: 100000 m: 10 i: 9
000009
z: 10000 m: 100 i: 10
000010
z: 10000 m: 100 i: 99
000099
z: 1000 m: 1000 i: 100
000100
z: 1000 m: 1000 i: 999
000999
z: 100 m: 10000 i: 1000
001000
z: 100 m: 10000 i: 9999
009999
z: 10 m: 100000 i: 10000
010000
z: 10 m: 100000 i: 99999
099999
z: 1 m: 1000000 i: 100000
100000
dash <<<'' 0.32s user 0.00s system 99% cpu 0.320 total
$m
선행 0 제거를 시작하려는 첫 번째 단계로 초기화되어야 합니다. m=1
위에서 방금 수행한 작업입니다. $z
위에서 언급했듯이 가장 높은 크기가 저장됩니다. $i
루프 수를 계산하므로 아마도 i=0
안전한 내기 일 것입니다. 어쨌든 나머지는 일어날 것입니다. 아이디어는 리드를 벗겨서 어디에나 부착하는 1
것 입니다.$z
set -- $(seq 1000); m=1 i=0 z=10000
while [ "$#" -gt 0 ] && : "$((z/=(m*=((i+=1)<$m?1:10))/$m))"
do printf 'mv "%s" "lead_string_%s"\n' "$1" "${z#1}$i${1#"${1%.*}"}"
shift; done
나는 printf
그것이 어떻게 보이는지 보여주기 위해 그것을 사용하고 있습니다. 물론 요점은 데이터를 인쇄하는 것을 피하고 대신 준비된 구분 인수로 현재 쉘에 넘겨주는 것입니다. 어쨌든 다음과 같습니다.
mv "1" "lead_string_0001"
mv "2" "lead_string_0002"
mv "3" "lead_string_0003"
...
mv "9" "lead_string_0009"
mv "10" "lead_string_0010"
mv "11" "lead_string_0011"
...
mv "15" "lead_string_0015"
...
mv "96" "lead_string_0096"
...
mv "99" "lead_string_0099"
mv "100" "lead_string_0100"
mv "101" "lead_string_0101"
mv "102" "lead_string_0102"
...
mv "998" "lead_string_0998"
mv "999" "lead_string_0999"
mv "1000" "lead_string_1000"
for
an을 사용하든지 loop를 사용하든 상관없습니다 while
. 따라서 파일의 경우 다음을 수행할 수 있습니다.
m=1 i=0 z=10000
for f in *; do : "$((z/=(m*=((i+=1)<$m?1:10))/$m))"
mv -- "$f" "lead_string_${z#1}$i${f#"${f%.*}"}"
done
답변3
이것은 나에게 효과적입니다. (그러나 rename
여기서 사용된 명령은 입니다 perl rename
.)
ls -1 -c | xargs rename -n 's/.*/our $i; sprintf("gg%04d", $i++)/e'
그러나 위의 내용은 이름에 공백이 포함된 파일이 존재하지 않는다고 가정합니다. 그렇지 않으면 깨질 것입니다. 또한 ls
출력을 구문 분석하는 것은 실제로 좋은 생각이 아닙니다.
다만, 파일명에 공백이 있으면 깨지기 때문에 위의 문제를 해결해 보도록 하겠습니다.
find -print0 | xargs -0 rename 's/.*/our $i; sprintf("gg%04d", $i++)/e'
위의 구문은 작동하지만 여전히 문제가 있습니다. 문제는 하위 디렉터리가 있을 때 하위 디렉터리의 이름도 변경된다는 것입니다. 이것이 바로 우리가 원하는 것입니다.
이 문제를 해결하기 위해 @terdon이 그의 의견에서 제안한 대로 명령을 다시 작성할 수 있습니다. 최종 수정된 명령은 다음과 같습니다.
find -maxdepth 1 -type f -print0 | xargs -0 rename 's/.*/our $i; sprintf("gg%04d", $i++)/e'
시험
내 폴더에 다음 파일이 있습니다.내 이름을 바꾸지 마세요이름을 바꾸고 싶지 않은 하위 디렉터리입니다.
ls
Don't rename me file1 file with spaces hello
이제 명령을 실행하고 다음 출력을 얻습니다.
find -maxdepth 1 -type f -print0 | xargs -0 rename -n 's/.*/our $i; sprintf("gg%04d", $i++)/e'
./file1 renamed as gg0000
./hello renamed as gg0001
./file with spaces renamed as gg0002
-n
최종 출력에 만족하면 플래그를 제거할 수 있습니다.
인용하다
답변4
각 반복마다 변수를 증가시키면 됩니다. rename
set 때문에 use strict
전역 변수를 사용해야 합니다 $::i
(또는 을 사용하거나 no strict
를 사용하여 변수를 선언해야 함 our
).
rename 'our $i; s/.*/gg_$i/; ++$i' *
숫자 앞에 0을 채우려면 파일 수를 세십시오. 파일 목록은 에 있습니다 @ARGV
.
rename 'our $i; my $width = length(scalar(@ARGV)); s/.*/sprintf("gg_%0${width}d", $i++)/e' *
첫 번째 반복에서만 코드를 실행하도록 카운터 변수가 정의되었는지 테스트할 수 있습니다.
rename 'our ($i, $width); if (!defined $width) {$width = length(scalar(@ARGV)); $i = 0;} s/.*/sprintf("gg_%0${width}d", $i++)/e' *
위의 모든 경우에서 디렉토리 부분이 포함된 파일 이름을 전달하는 경우(즉, /
여기서는 발생하지 않는 - 가 포함된 파일 이름을 전달하는 경우 *
) s/.*/…/
로 변경합니다 s/[^/]*\z/…/
.