Группировать файлы и каталоги по первой букве их имени и перемещать их в каталоги с этой буквой

Следующее документировано вman btrfs-subvolume:

For every subvolume the following information is shown by default: ID, gen, top level, and path where:

ID is subvolume’s id,

gen is an internal counter which is updated every transaction

top level is the same as parent subvolume’s id

and path is the relative path of the subvolume to the top level subvolume.

Рассмотрим эти примеры из вашего вопроса:

ID 256 gen 479 parent 5 top level 5 path @
ID 258 gen 479 parent 256 top level 256 path var

Подтом @ имеет родителя 5, потому что это идентификатор подтома верхнего -уровня (, не показанный в списке ). Subvolume var имеет родителя 256, потому что он находится внутри @ subvolume. Да, родитель и «верхний уровень» должны быть одинаковыми.

3
16.04.2021, 15:17
4 ответа

Просто переберите все вещи:

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

-pговорит mkdirне жаловаться, если каталог уже существует, а ${f:0:1}является синтаксисом Ksh/Bash/Zsh для «получить первый символ».

И вот он в действии:

$ 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
3
28.04.2021, 22:52

Во-первых, решение для вашего шаблона перемещения простое, просто добавьте ?. Это соответствует только a, за которым следует как минимум еще один символ.

mv a?* a

У нас не так много букв, но чтобы найти все реально присутствующие буквы, вы можете использовать эту команду. sedудаляет все после первой буквы, sort uniq удаляет дубликаты.

ls | sed -r 's/^(.).*$/\1/' | sort -u
0
28.04.2021, 22:52

POSIXly:

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

Другой подход, который не был упомянут, состоит в том, чтобы найти все уникальные первые буквы файлов и каталогов, переместить все во временный каталог, создать каталог с первой буквой, а затем вернуть все обратно.

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"
1
28.04.2021, 22:52

Теги

Похожие вопросы