이름의 첫 글자를 기준으로 파일과 디렉터리를 그룹화하고 해당 글자가 있는 디렉터리로 이동합니다.

이름의 첫 글자를 기준으로 파일과 디렉터리를 그룹화하고 해당 글자가 있는 디렉터리로 이동합니다.

다음 내용이 포함된 디렉토리가 있습니다.

$ mkdir dir && cd "$_"
~/dir $ mkdir a1 a2 a3 a4 b1 c1 c2 1a 2a 2b 2c 2d 3a _1 _2
~/dir $ touch a_1 a_2 a_3 a_4 b_1 c_1 c_2 1_a 2_a 2_b 2_c 2_d 3_a __1 __2
~/dir $ ls
__1  1_a  __2  2_a  2_b  2_c  2_d  3_a  a_1  a_2  a_3  a_4  b_1  c_1  c_2
_1   1a   _2   2a   2b   2c   2d   3a   a1   a2   a3   a4   b1   c1   c2

이제 이 모든 파일과 디렉터리를 그룹화하고 싶습니다.그들의 첫 번째 편지에 따르면그리고 그것들을 다음으로 옮깁니다.같은 문자를 가진 디렉토리. 따라서 출력은 다음과 같습니다.

~/dir $ ls
_  1  2  3  a  b  c

그리고 사용엑사, 트리는 다음과 같습니다.

~/dir $ exa --tree
.
├── 1
│  ├── 1_a
│  └── 1a
├── 2
│  ├── 2_a
│  ├── 2_b
│  ├── 2_c
│  ├── 2_d
│  ├── 2a
│  ├── 2b
│  ├── 2c
│  └── 2d
├── 3
│  ├── 3_a
│  └── 3a
├── _
│  ├── _1
│  ├── _2
│  ├── __1
│  └── __2
├── a
│  ├── a1
│  ├── a2
│  ├── a3
│  ├── a4
│  ├── a_1
│  ├── a_2
│  ├── a_3
│  └── a_4
├── b
│  ├── b1
│  └── b_1
└── c
   ├── c1
   ├── c2
   ├── c_1
   └── c_2

와일드카드를 사용하여 이동할 수 있다는 것을 알고 있습니다.

~/dir $ mkdir a && mv a* a

오류가 발생합니다.

mkdir: cannot create directory ‘a’: File exists

그러나 그것은 일을 완수합니다. 오류를 피하기 위해 이렇게 할 수 있습니다.

~/dir $ mkdir temp && mv a* temp && mv temp a

그런 다음 이것을 for 루프에서 사용하여 내가 아는 모든 문자를 처리할 수 있습니다. 하지만 문제는 그 첫 글자가 무엇인지 모른다는 것입니다. 우리는 많은 글자를 가지고 있습니다. 글자를 몰라도 이를 달성할 수 있는 방법이 있나요?

답변1

모든 것을 반복하십시오.

#!/bin/bash
for f in *; do 
    firstChar="${f:0:1}"; 
    mkdir -p -- "$firstChar"; 
    mv -- "$f" "$firstChar"; 
done

디렉토리가 이미 존재하고 Ksh/Bash/Zsh 구문 "첫 번째 문자 가져오기"가 사용되는 경우 -p불평하지 말라고 지시합니다 .mkdir${f:0:1}

작동 중입니다.

$ mkdir a1 a2 a3 a4 b1 c1 c2 1a 2a 2b 2c 2d 3a _1 _2
$ touch a_1 a_2 a_3 a_4 b_1 c_1 c_2 1_a 2_a 2_b 2_c 2_d 3_a __1 __2
$ ls -F
__1  _1/  1_a  1a/  __2  _2/  2_a  2a/  2_b  2b/  2_c  2c/  2_d  2d/  3_a  3a/  a_1  a1/  a_2  a2/  a_3  a3/  a_4  a4/  b_1  b1/  c_1  c1/  c_2  c2/

$ for f in *; do 
    firstChar="${f:0:1}"; 
    mkdir -p "$firstChar"; 
    mv -- "$f" "$firstChar"; 
done

$ tree
.
├── _
│   ├── __1
│   ├── _1
│   ├── __2
│   └── _2
├── 1
│   ├── 1_a
│   └── 1a
├── 2
│   ├── 2_a
│   ├── 2a
│   ├── 2_b
│   ├── 2b
│   ├── 2_c
│   ├── 2c
│   ├── 2_d
│   └── 2d
├── 3
│   ├── 3_a
│   └── 3a
├── a
│   ├── a_1
│   ├── a1
│   ├── a_2
│   ├── a2
│   ├── a_3
│   ├── a3
│   ├── a_4
│   └── a4
├── b
│   ├── b_1
│   └── b1
└── c
    ├── c_1
    ├── c1
    ├── c_2
    └── c2

22 directories, 15 files

기존 파일이나 디렉터리 이름에 문자가 하나만 있으면 오류가 발생합니다. 예를 들어, 이라는 파일을 만들고 a위 방법을 시도하면 다음과 같은 결과를 얻을 수 있습니다.

mkdir: cannot create directory ‘a’: File exists
mv: 'a' and 'a' are the same file

이것이 문제인 경우 임시 파일 이름 바꾸기, 디렉토리 생성 및 파일 이동과 같은 복잡한 작업을 수행할 수 있습니다.

for f in *; do 
    firstChar="${f:0:1}";
    ## if the first character exists as a dir, move the file there
    if [[ -d "$firstChar" ]]; then
       mv -- "$f" "$firstChar"; 
    ## if it exists but is not a dir, move to a temp location,
    ## re-create it as a dir, and move back from temp
    elif [[ -e "$firstChar" ]]; then
        tmp=$(mktemp ./tmp.XXXXX)
        mv -- "$firstChar" "$tmp"
        mkdir -p "$firstChar"
        mv -- "$tmp" "$firstChar"/"$f"
    else    
        mkdir -p "$firstChar"; 
        mv -- "$f" "$firstChar"; 
    fi
done

답변2

POSIX적으로:

find . -path './*' -prune -exec sh -c '
    j="${1%${1#???}}"; mkdir "$j" && mv -- "$1" "$j/"
' _ {} \; 2>/dev/null

답변3

언급되지 않은 또 다른 방법은 파일과 디렉터리의 고유한 첫 글자를 모두 찾아서 모든 것을 임시 디렉터리로 이동하고 첫 글자 디렉터리를 만든 다음 모든 것을 다시 이동하는 것입니다.

letters=($(ls -1 | sort -n | cut -c 1 | uniq))
temp=$(mktemp -dp .)
for letter in "${letters[@]}"; do
    mv "$letter"* "$temp";
    mkdir "$letter";
    mv "$temp/"* "$letter";
done
rm -r "$temp"

이런 식으로,단일 문자 디렉터리 및 파일또한 처리되었습니다. 실행 중:

$ mkdir a1 a2 a3 a4 b1 c1 c2 1a 2a 2b 2c 2d 3a _1 _2
$ touch a_1 a_2 a_3 a_4 b_1 c_1 c_2 1_a 2_a 2_b 2_c 2_d 3_a __1 __2
$ mkdir a b c
$ touch 1 2 3 _
$ ls -F
_    1    __2  2_a  2b/  2_d  3_a  a_1  a2/  a_4  b_1  c_1  c2/
__1  1_a  _2/  2a/  2_c  2d/  3a/  a1/  a_3  a4/  b1/  c1/
_1/  1a/  2    2_b  2c/  3    a/   a_2  a3/  b/   c/   c_2

이 스크립트 이후의 결과는 다음과 같습니다.

$ ls -F
_/  1/  2/  3/  a/  b/  c/
$ exa --tree
.
├── 1
│  ├── 1
│  ├── 1_a
│  └── 1a
├── 2
│  ├── 2
│  ├── 2_a
│  ├── 2_b
│  ├── 2_c
│  ├── 2_d
│  ├── 2a
│  ├── 2b
│  ├── 2c
│  └── 2d
├── 3
│  ├── 3
│  ├── 3_a
│  └── 3a
├── _
│  ├── _
│  ├── _1
│  ├── _2
│  ├── __1
│  └── __2
├── a
│  ├── a
│  ├── a1
│  ├── a2
│  ├── a3
│  ├── a4
│  ├── a_1
│  ├── a_2
│  ├── a_3
│  └── a_4
├── b
│  ├── b
│  ├── b1
│  └── b_1
└── c
   ├── c
   ├── c1
   ├── c2
   ├── c_1
   └── c_2

이렇게 하면 필요한 디렉터리만 생성됩니다. 따라서 mkdir명령은 가능한 한 적은 횟수로 실행됩니다.

편집 : 같이@terdon이 언급됨, 구문 분석된 출력으로 인해 해당 이름의 파일에서 ls명령이 실패하게 됩니다 . newline이 문제를 해결하기 위해 다음을 사용할 수 있습니다.

temp=$(mktemp -dp .)
for f in *; do
    letter="${f:0:1}";
    mv -- "$letter"* "$temp";
    mkdir "$letter";
    mv "$temp/"* "$letter";
done
rm -r "$temp"

답변4

그리고 zsh:

autoload -Uz zmv # best in ~/.zshrc
() { mkdir ${(uM)@#?}; } ??* && zmv '(?)?*' '$1/$f'

우리는:

  • () { body; } args: 확장자 ??*(2자 이상으로 구성된 파일 이름)를 인수로 전달하는 익명 함수입니다. 신체 내:
  • ${@#?}args에서 첫 번째 문자가 제거되지만 M인수 확장 플래그(일치에 사용됨)를 사용하면 상황이 반전되어 첫 번째 문자만 유지됩니다. u(고유한 경우) 중복을 제거합니다.
  • 그런 다음 zmv '(?)?*' '$1/$f'최소 2개의 문자가 포함된 파일을 해당 폴더로 이동합니다.

관련 정보