拡張子(zsh、macOS)に従ってファイルをディレクトリに移動します。

拡張子(zsh、macOS)に従ってファイルをディレクトリに移動します。

~/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より良いかもしれません。

についてfindmv現在のディレクトリでのみ操作を実行したいです。、そして何らかの理由で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に関連するものは追加しません。いいえ特に、独自のプラットフォームからオープンソースのプラットフォームに切り替えて実際に収容できる場合は、1つのプラットフォームに接続してください。Unixの哲学(私は説教しないようにしています。これは効果的ですか?)

答え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$2csh$f:t:r$f:ezmv$f

zmv '**/*.(#i)(jpg|png)(#qD.)' '$HOME/$f:e:l/$f:t:r'

findまた、ディレクトリを2回クロールしないように、1回の呼び出しでこれを行うことも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 ffind-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

pxeger 言う

-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では確かに動作します。)

関連情報