~/jpg
모든 파일을 이미 가지고 있는 디렉토리 구조( , ~/png
, ~/mp3
, 등) 로 자동으로 이동하는 스크립트를 원합니다 ~/zip
. 지금까지 이것은 내가 원하는 것과 거의 정확히 일치합니다.
#!/bin/zsh
echo "Executing Script"
find . -iname '*.jpg' -exec gmv -i --target-directory=$HOME/jpg '{}' +
find . -iname '*.png' -exec gmv -i --target-directory=$HOME/png '{}' +
나는 쉘 스크립팅에 대한 경험이 없기 때문에 이것은 내가 함께 엮은 것입니다.
단순히 연속된 라인에 연속 명령을 실행하는 것이 쉘용 스크립트를 작성하는 올바른 방법입니까? 처음에 이것을 시도했는데 mv
오류 처리가 필요했고 우아하지도 않았습니다. 연장하려고 해요사용자 EvilSoup의 답변은 여기에 있습니다.
이미 언급한 것 외에도 mv -i
플래그 도 포함했습니다.이렇게 하면 이미 존재하는 항목을 덮어쓰지 않습니다.(이 부분이 매우 중요합니다.) 어쩌면 이것이 -n
더 나을지도 모르겠습니다.
에 대한 find
,mv
현재 디렉터리에서만 작업이 수행되기를 원합니다., 그리고 어떤 이유로 find
제한하는 방법을 잘 이해하지 못하지만 약간 재귀적인 것처럼 보입니다. 모든 디렉터리에서 스크립트를 실행하고 mv
현재 작업 디렉터리에 있는 파일 만 포함하고 싶습니다 .find
zsh-on-macOS-의 경우 +1특정한소개 자료: 쉘 스크립트.
답변1
당신은 아무 잘못도 하지 않았습니다. 스크립트 작성을 시작하면 첫 번째 스크립트는 명령이 차례대로 나열된 목록일 뿐이며 이는 완전히 정상입니다.
하지만 그 이상으로 나아가는 방법을 배울 수 있기를 바랍니다. 당신의 대본이 나쁘다는 말은 아니지만, 비교해서 당신의 대본 대신 내가 쓰는 것을 보여주고 싶습니다. 조건부, 루프 및 안전 검사를 사용하고 스크립트가 수행하는 작업을 확인할 수 있도록 여러 주석을 추가했습니다.
#!/usr/bin/env zsh
# announce that the script has started
echo "Running sorting script."
# the variable "$1" means the first argument to the script, i.e., if
# the script is named "mysorter.zsh" if you ran mysorter.zsh ~/tmp
# then $1 would be "~/tmp"
# I'm assigning that to the variable "argument"
argument="$1"
# if $argument is blank, I want the script to run in the present
# directory; if not, I want to move to that directory first
# [[ -n "$argument" ]] means "$argument is not blank" (not length 0)
# for other conditions like -n, see
# https://zsh.sourceforge.io/Doc/Release/Conditional-Expressions.html
if [[ -n "$argument" ]] ; then
# [[ -d "$argument" ]] checks if "$argument" is a directory; if it's
# not, the command is a mistake and the script should quit
# the "!" means "not"
if [[ ! -d "$argument" ]] ; then
# echo prints text; the ">&2" means print to the standard error
# stream, rather than regular output stream, because this is an
# error message
echo "$argument either does not exist or is not a directory" >&2
# exit quits the script; the number indicates the error code; if
# you write "exit 0" that means no error; but this is an error
# so I give it a non-zero code
exit 1
fi
# if we made it here, then "$argument" is indeed a folder, so I
# move into it
cd "$argument"
fi
# this is a loop that will be separately processed for all files in
# the active directory
for file in * ; do
# I indent inside the loop to indicate better where the loop starts
# and stops
# first I check if "$file" is a folder/directory; if it is,
# I don't want to do anything; "continue" means cease this cycle
# through the loop and move on to the next one
if [[ -d "$file" ]] ; then
continue
fi
# what I want to do next depends on the extension of "$file";
# first I will determine what that is
# the notatation "${file##*.}" means cut off everything prior to
# the final . ;
# see https://zsh.sourceforge.io/Doc/Release/Expansion.html#Parameter-Expansion
extension="${file##*.}"
# i want to treat extensions like JPG and jpg alike, so I will make
# the extension lowercase; see the same page above
extension="${(L)extension}"
# I want to move the file to a folder in $HOME named after the
# extension, i.e., $HOME/$extension; first I check if it doesn't
# exist yet
if [[ ! -d "$HOME/$extension" ]] ; then
# since it doesn't exist, I will create it
mkdir "$HOME/$extension"
fi
# by default we want the move target to have the same name
# as the original file
targetname="$file"
# but I may need to add a number to it; see below
num=0
# now I want to check if there is already a file in there with
# that name already, and keep changing the target filename
# while there is
while [[ -e "$HOME/$extension/$targetname" ]] ; do
# a file with that name already exists; but I still want
# to put the file there without overwriting the other one;
# I will keep checking numbers to add to the name until I
# find one for a file that doesn't exist yet
# increase by one
let num++
# the new targetname is filename with the extension cut off,
# plus "-$num" plus the extension
# e.g. "originalfilename-1.zip"
targetname="${file%.*}-${num}.${extension}"
done
# we have now found a safe file name to use for the target,
# so we can move the file
echo "Moving file $file to $HOME/$extension/$targetname"
# here we try to move the file, but if it fails, we
# print an error and quit the script with an error
if ! mv "$file" "$HOME/$extension/$targetname" ; then
echo "Move command failed!" >&2
exit 1
fi
# we're now done with this file and can move on to the next one
done
# done indicates the end of the loop
#
# if we got here everything was successful and quit with exit code 0
echo "All files successfully sorted."
exit 0
이것은 단지 가능한 것에 대한 아이디어를 제공하기 위한 것입니다.
스크립트가 바로 완벽해질까 걱정하지 마세요. 더 많이 연습할수록 더 좋아질 것입니다. 이 스크립트가 귀하에게 적합하다면 귀하에게 적합한 것입니다.
분명히 링크를 몇개 추가했는데https://zsh.sourceforge.io이것은 zsh에 대해 자세히 알아볼 수 있는 좋은 자료입니다.
배우려고 노력해야하므로 Mac과 관련된 내용은 추가하지 않겠습니다.아니요특히 독점 플랫폼에서 오픈 소스 플랫폼으로 전환하고 실제로 수용할 수 있는 경우에는 하나의 플랫폼에 연결하십시오.유닉스 철학(저는 설교하지 않으려고 노력합니다. 이것이 효과적입니까?)
답변2
스크립트에 특별히 잘못된 점은 없습니다(첫 번째 스크립트가 find
실패하면 스크립트의 종료 상태에 보고되지 않는다는 점만 제외).
그러나 당신이 그것을 사용하고 있기 때문에 zsh
그것을 개선하거나 zsh와 더 비슷하게 만들 수 있는 방법이 있습니다. 예를 들어zmv
이것은 zsh의 자체 일괄 이름 바꾸기 도구입니다(자동 로드 기능으로 구현됨).
#! /bin/zsh -
autoload zmv
zmodload zsh/files # for builtin mv to speed things up as zmv
# is going to run one mv per file below
zmv '**/(*).(#i)(jpg|png)(#qD.)' '$HOME/$2:l/$1'
zmv
이름 바꾸기를 시작하기 전에 데이터 손실을 방지하기 위해 일부 온전성 검사가 수행됩니다(대상이 이미 존재하거나 두 파일이 동일한 대상으로 확인되어 파일을 덮어쓰기 때문).
**/
(을 위한재귀 와일드카드)는 모든 수준의 하위 디렉터리와 일치합니다. 현재 디렉터리의 파일만 이동하려면 해당 파일을 삭제하면 됩니다.
(#q...)
소개글로벌 예선D
도트 파일을 건너뛰지 말고 .
일치 항목을 다음으로 제한하십시오.정기적인파일(디렉토리, 심볼릭 링크, fifos... 제외). 약간의 성능 최적화로 oN
파일 N
목록 에 한정자를 추가 할 수도 있습니다 .o
(#i)
대소문자를 구분하지 않는 일치를 활성화합니다.
$2:l
$2
소문자로 변환합니다 . 이것은 (t)csh 스타일을 사용하고 있습니다.수정자( 에서는 가 tcsh
필요합니다 $2:al
). zsh에서는 다음을 사용할 수도 있습니다.매개변수 확장 플래그( ${(L)2}
).
다음에서 기본 이름의 루트와 확장자를 캡처( (
... 사용)하는 대신 전체 파일 경로(변수에서 사용 가능하게 함)에서 -style , (tail of tail)(확장자)을 사용할 수도 있습니다.)
$1
$2
csh
$f:t:r
$f:e
zmv
$f
zmv '**/*.(#i)(jpg|png)(#qD.)' '$HOME/$f:e:l/$f:t:r'
find
및 를 사용하면 디렉터리를 두 번 크롤링하지 않도록 한 번의 호출로 이 작업을 수행 gmv
할 수도 있습니다 .find
find . -type f '(' -name '*.[jJ][pP][gG]' -exec gmv -it ~/jpg {} + -o \
-name '*.[pP][nN][gG]' -exec gmv -it ~/jpg {} + ')'
-iname
(또는 find
구현이 비표준 확장을 지원 하는 경우 재귀를 중지하려면 ! -name . -prune
앞에 . 를 추가하세요. 일부 구현 도 이를 지원하지만 (깊이 0에서) 패턴이 어쨌든 일치하지 않기 때문에 여기서는 필요하지 않습니다 .)-type f
find
-mindepth 1 -maxdepth 1
-mindepth 1
.
GNU 없이도 mv
할 수 있습니다sh
대상 디렉터리의 매개변수를 끝까지 재정렬하는 데 사용됩니다 .:
find . -type f '(' -name '*.[jJ][pP][gG]' -exec sh -c '
exec mv -i "$@" ~/jpg/' sh {} + -o \
-name '*.[pP][nN][gG]' -exec sh -c '
exec mv -i "$@" ~/png/' sh {} + ')'
( -type f
위의 glob 한정자와 동일합니다 zsh
) .
.
상호작용에 대한 온전성 검사만 -i
수행되며 사전에 수행되지는 않습니다.
확장 + 대상 디렉터리 목록을 기반으로 명령을 작성하려면 다음을 수행할 수 있습니다.
cmd=( find . -type f '(' ) or=()
for ext dir (
jpg ~/jpg
jpeg ~/jpg
png ~/png
aif ~/aiff
) cmd+=($or -iname "*.$ext" -exec gmv -it $dir {} +) or=(-o)
cmd+=(')')
$cmd
이 zmv
방법으로:
typeset -A map=(
jpg ~/jpg
jpeg ~/jpg
png ~/png
aif ~/aiff
)
zmv "**/(*).(#i)(${(kj[|]map})(#qD.)" '$map[$2:l]/$1'
$map
확장을 대상 디렉터리에 매핑하려면 연관 배열을 사용하세요 . 연관 배열의 ey와 s를 ${(kj[|])map}
j
결합합니다 . 에 전달된 패턴 매개변수로 확장하기 위해 큰따옴표로 전환합니다 .k
|
zmv
¹ 다음 유형의 파일이 있는 경우 또 다른 잠재적인 문제가 발생합니다.목차여기에 표시된 대로 해당 패턴이나 dir.png/subdir.png/file.png
건너뛰고 싶은 패턴 또는 더 나쁜 항목을 일치시키거나 일반 파일로만 제한하세요.! -type d
답변3
스크립트의 복잡성과 우아함에 대해 질문하셨으므로 추가하겠습니다.프레이어스의 답변당신은 다른 것들과 함께할 수 있다하다:
︙
echo "Executing Script"
for ext in jpg png
do
find . -maxdepth 1 -iname "*.$ext" -exec gmv -n --target-directory="$HOME/$ext" {} +
done
말했듯이 여러 확장명(예: mp3, zip, bmp, gif 등)을 사용하여 동일한 작업을 수행하는 경우 코드 중복을 줄일 수 있습니다. 나는 bash 사용자에게 말한다
너~해야 한다표시된 대로 모든 쉘 변수( )를 큰따옴표로 묶습니다(보시다시피 때로는 따옴표 없이도 잘 작동합니다).
$something
-y
옵션 을 설정하지 않는 한 Zsh에서는 (대부분의 경우) 필요하지 않습니다.
하지만 구체적으로 당신은~ 해야 하다아니요*.$ext
인용하다하나의따옴표는 쉘 변수의 올바른 해석을 방해하기 때문입니다 $ext
. 당신은요~ 해야 하다쉘이 glob을 확장하는 것을 방지하려면 작은따옴표 *
, 큰따옴표 또는 로 인용하십시오 . 확장 없이 그대로 전달해야 합니다.\*
find
인용문은 필요하지 않습니다 {}
(그러나 아프지는 않습니다).
jpeg
이는 다중 철자(예: → jpg
및 aif
→ ) 문제를 직접적으로 지원하지는 않지만 aiff
(적어도 쉽지는 않습니다) 심볼릭 링크를 사용하여 스크립트 외부(파일 시스템)에서 처리할 수 있습니다.
CD #즉,CD "$HOME" mkdir jpg png mp3 zip aiff… ln -s jpg jpeg ln -s aiff aif
그럼 해
for ext in jpg jpeg png aif aiff …
do
find . -maxdepth 1 -iname "*.$ext" -exec gmv -n --target-directory="$HOME/$ext" {} +
done
예, -iname
테스트는 find
좋습니다. 하지만 꽤 쉽게 시뮬레이션할 수 있습니다.
gmv -n -- *.[Jj][Pp][Gg] *.[Jj][Pp][Ee][Gg] "$HOME"/jpg
그러나 이를 반복하는 것은 그리 쉬운 일이 아닙니다 for
. 적어도 bash에서는 그렇지 않습니다. 분명히 zsh에서는 이것이 쉽습니다.Stefan Chazeras의 답변. Stéphane은 zsh에서 glob(와일드카드)이 실패하면 다른 glob이 성공하더라도 전체 명령이 중단된다는 점을 상기시켰습니다. 따라서 zsh의 경우 위의 내용을 다음과 같이 분할해야 합니다.
gmv -n -- *.[Jj][Pp][Gg] "$HOME"/jpg
gmv -n -- *.[Jj][Pp][Ee][Gg] "$HOME"/jpg
저는 zsh에 대해 잘 모르지만 위에서 보여드린 모든 내용이 zsh에서 작동할 것이라고 믿습니다. (Bash에서는 확실히 작동합니다.)