나는 매우 부족한 bash 지식을 사용하여 JPG/PNG 파일을 JPEG XL로 일괄 변환하기 위해 만든 다음 bash 스크립트를 가지고 있습니다. 지금까지 스크립트는 내 요구에 따라 아무런 문제 없이 잘 작동하고 있습니다.
내가 해결할 수 없는 유일한 문제는 이미지에 "시각적 무손실" JPEG XL과 호환되지 않는 ICC 프로필이 있는지 확인하는 최적화의 반복 부분입니다.
내 초기 아이디어는 Find 및 Parallel을 IF 및 ELSE와 결합하는 것이었지만 성공하지 못했고 많은 구문 오류 출력이 발생했기 때문에 대안으로 루프를 사용하기로 선택했지만 여러 파일이 있는 폴더에서 확인 과정이 느리고 때로는 변환 자체보다 시간이 더 오래 걸리기 때문에 스크립트의 이 부분을 어떻게 최적화할 수 있는지 묻습니다.
#!/bin/bash
# create a copy of all folders and subfolder inside a path called jxl #
find . -type d -not -path "./jxl/*" -exec mkdir -p ./jxl/{} \; -exec mkdir -p ./jxl/icc/{} \;
rmdir ./jxl/jxl
rmdir ./jxl/icc/jxl
# move images with a NOT compatible icc profile to a directory called icc inside jxl path #
dir="./jxl/icc"
icc1="Device Model : "
icc2="Device Model : NONE"
icc3="Device Model : MS30"
shopt -s globstar
for f in **/*.jpg **/*.jpeg **/*.jpe **/*.png
do
check=$(exiftool -devicemodel "$f")
if [ "$check" = "$icc1" ] || [ "$check" = "$icc2" ] || [ "$check" = "$icc3" ]; then
echo "$f = icc profile NOT compatible"
mv "$f" "$dir/$f"
else
echo "$f = icc profile compatible"
fi
done
# Run cjxl encoder e ignore all files inside the jxl folder
find ./ -type f \( -iname \*.jpg -o -iname \*.jpeg -o -iname \*.jpe -o -iname \*.png \) -not -path "./jxl/*" -print0 | parallel --jobs 8 -0 cjxl '{}' './jxl/{.}.jxl' -d 1 -e 7 -E 3 -I 1 --lossless_jpeg 0\;
# copy all files that are not a image to the jxl folder
find ./ -type f \( -iname \*.* ! -iname \*.jpg ! -iname \*.jpeg ! -iname \*.jpe ! -iname \*.png ! -iname \*.sh ! -iname \*.html \) -not -path "./jxl/*" -print0 | parallel --jobs 5 -0 mv '{}' './jxl/{}' \;
#delete all empty folders inside the jxl folder
find ./jxl -type d -empty -delete
답변1
내가 볼 수 있는 유일한 확실한 개선점은 외부 프로그램을 여러 번 포크하는 것을 피하는 것입니다. 이것이 "수정"할 가치가 있는지 여부는 각 명령이 분기된 횟수에 따라 달라 find
지거나 bash
... 단지 소수 또는 수십 개의 파일인 경우 아마도 그렇지 않을 것입니다. 수백, 수천이라면 확실히 그렇습니다.
예를 들어, 첫 번째 항목에서는 find
다음을 실행 중입니다.mkdir
두 배발견된 각 파일에 대해. 다음과 같이 작성하여 최적화할 수 있습니다.
find . -type d -not -path "./jxl/*" \
-exec bash -c 'for d; do
printf './jxl/%s\0./jxl/icc/%s\0' "$d" "$d";
done | xargs -0r mkdir -p ' bash {} +
printf
이는 사용할 NUL로 구분된 디렉터리 목록을 보내는 데 사용됩니다 xargs -0r mkdir -p
.
나중에 스크립트의 루프에서 각 비ICC 호환 파일에 대해 이 작업을 한 번씩 수행 for f in **/*.jpg **/*.jpeg **/*.jpe **/*.png
합니다 . mv
이동하려는 파일이 포함된 배열을 구축 하고 printf
.xargs -0r mv -t "$dir/"
예를 들어:
declare -a mvfiles=()
for f in **/*.jpg **/*.jpeg **/*.jpe **/*.png
do
check=$(exiftool -devicemodel "$f")
if [ "$check" = "$icc1" ] || [ "$check" = "$icc2" ] || [ "$check" = "$icc3" ]; then
echo "$f = icc profile NOT compatible"
mvfiles+=("$f")
else
echo "$f = icc profile compatible"
fi
done
printf '%s\0' "${mvfiles[@]}" | xargs -0r mv -t "$dir/"
이는 대상 디렉터리를 지정할 수 있는 옵션 mv
과 함께 GNU를 사용한다고 가정합니다.-t
앞으로모든 소스 경로 이름. ' 옵션을 mv
사용해야 하고 이는 파일 이름당 하나의 옵션을 실행하는 목적을 상실하므로 다른 버전에서는 이 작업을 수행할 가치가 없습니다 . 물론 FreeBSD 버전의 with 옵션을 사용하지 않는 한(유사하게 작동 하지만 명령당 여러 인수를 허용함)xargs
-I
mv
xargs
-J
-I
xargs
없이 그냥 사용할 수 있지만 mv "${mvfiles[@]}" "$dir/"
수천 개의 파일이 있으면 ARG_MAX를 초과할 위험이 있습니다. xargs를 사용하면 위험을 피할 수 있습니다.
parallel
그런데, 이 문제를 어떻게 처리 해야 할지 잘 모르겠습니다 {}
(주로 병렬 작업에 사용합니다 xargs -P
). 하지만 조사해 보고 싶을 수도 있습니다. 5개의 프로세스를 동시에 실행 하려는 경우 parallel
각 매개변수에 대해 하나의 명령을 실행하게 될 것입니다. mv
이것이 바로 런타임에 필요한 것일 수 있습니다 cjxl
(저는 설치하지 않았고 파일 이름 인수를 어떻게 처리하는지 모르기 때문에 설명할 수 없습니다). 하지만 파이프를 사용하여 사용하는 mv
것이 더 좋습니다.mv -t
xargs -0r mv -t ./jxl/
또한 parallel
한 번에 실행되는 여러 프로세스를 사용하면 기대하는 성능 향상을 제공하지 못할 수도 있다는 점도 주목할 가치가 있습니다. 이는 프로세스가 I/O 또는 사용 가능한 CPU 전력에 의해 제한되는지 여부에 따라 달라집니다.
그렇다면아니요I/O 대역폭이 부족한 경우 병렬로 실행하면 성능이 크게 향상됩니다.
프로세스에 I/O가 부족하면 입력을 기다리는 동안 대부분의 시간을 유휴 상태로 보내게 됩니다. 특히 IO를 위해 서로 경쟁하게 되는 경우에는 더욱 그렇습니다. 그럼 실험을 통해 알아보세요최적의 수cjxl
하드웨어의 사용 가능한 IO 대역폭을 초과하지 않고 실행할 수 있는 작업 수 - 8가능한정확한 숫자이거나 그보다 적을 수도 있습니다. 코어가 8개보다 많으면 더 많을 수도 있지만 그럴 가능성은 거의 없습니다.
답변2
GNU Parallel에서 호출되는 단일 bash 함수를 포함하도록 스크립트를 변경하는 것이 좋습니다.
doit() {
# Do all processing of a single file here
# including if statements
}
export -f doit
find ... | parallel doit
이것의 장점은 한 번에 하나의 파일에 대해 기능을 테스트할 수 있다는 것입니다.
또 다른 이점은 CPU와 I/O 집약적인 부분을 혼합할 수 있다는 것입니다. 따라서 운이 좋다면 한 작업은 CPU를 사용하고 다른 작업은 디스크를 사용하게 됩니다.