Зрівняти каталог, але зберегти імена каталогів у новому імені файлу


11

Як можна вирівняти каталог у наступному форматі?

Перед: ./aaa/bbb/ccc.png

Після: ./aaa-bbb-ccc.png


PS: Будь ласка, врахуйте імена файлів із непарними символами (наприклад, пробіли)
Nathan JB

3
Вам також потрібно буде вказати, як ви хочете розглянути можливий випадок, коли ./foo/bar/baz.pngі те й ./foo-bar-baz.pngінше існує. Я припускаю, що ви не хочете замінювати останню на першу?
jw013

У моєму випадку це не має значення, але відповідь справді повинна пояснювати це, щоб інші могли на це покладатися! Дякую!
Nathan JB

Відповіді:


7

Попередження: Більшість цих команд я набрав безпосередньо у своєму браузері. Кавеат лектор.

З zsh та zmv :

zmv -o -i -Qn '(**/)(*)(D)' '${1//\//-}$2'

Пояснення: Шаблон **/*відповідає всім файлам у підкаталогах поточного каталогу, рекурсивно (він не відповідає файлам у поточному каталозі, але їх не потрібно перейменовувати). Перші дві пари круглих дужок представляють собою групи , які можуть бути позначатися як $1і $2в тексті заміни. Кінцева пара дужок додає D глобальний класифікатор, щоб файли крапок не були опущені. -o -iозначає передати -iпараметр mvтак, щоб вам було запропоновано, якщо існуючий файл буде перезаписаний.


За допомогою лише інструментів POSIX:

find . -depth -exec sh -c '
    for source; do
      case $source in ./*/*)
        target="$(printf %sz "${source#./}" | tr / -)";
        mv -i -- "$source" "${target%z}";;
      esac
    done
' _ {} +

Пояснення: caseоператор опускає поточний каталог та підкаталоги верхнього рівня поточного каталогу. targetмістить ім'я вихідного файлу ( $0) з провідним ./викресленим та всіма косою рисою, заміненою тире, плюс заключною z. Фінал zє в тому випадку, якщо ім'я файлу закінчується новим рядком: інакше заміна команди зніме його.

Якщо ваш findне підтримує -exec … +(OpenBSD, я дивлюся на вас):

find . -depth -exec sh -c '
    case $0 in ./*/*)
      target="$(printf %sz "${0#./}" | tr / -)";
      mv -i -- "$0" "${target%z}";;
    esac
' {} \;

З bash (або ksh93) вам не потрібно викликати зовнішню команду для заміни косої риски на тире, ви можете використовувати розширення параметра ksh93 за допомогою конструктивної заміни рядків ${VAR//STRING/REPLACEMENT}:

find . -depth -exec bash -c '
    for source; do
      case $source in ./*/*)
        source=${source#./}
        target="${source//\//-}";
        mv -i -- "$source" "$target";;
      esac
    done
' _ {} +

У for sourceприкладах пропускається перший представлений файл / реж ... Я нарешті з’ясував, чому ви (як правило) використовували _як $0:) ... і дякую за чудові відповіді. Я так багато вчусь у них.
Пітер.O

Зауважте, що приклад zmv просто виконує сухий запуск: він виводить команди переміщення, але він не виконує їх. Щоб реально виконати ці команди, видаліть n в -Qn.
Пітер

5

Хоча тут вже є хороші відповіді, я вважаю це більш інтуїтивним у межах bash:

find aaa -type f -exec sh -c 'new=$(echo "{}" | tr "/" "-" | tr " " "_"); mv "{}" "$new"' \;

Де aaaваш існуючий каталог. Файли, які вони містять, будуть переміщені до поточного каталогу (залишаючи лише порожні каталоги в aaa). Два дзвінки для trобробки як каталогів, так і пробілів (без логіки для уникнутих косої риски - вправа для читача).

Я вважаю це більш інтуїтивно зрозумілим і відносно легким для підключення. Тобто я міг би змінити findпараметри, якщо скажу, що хочу знайти лише .png файли. Я можу змінити trдзвінки або додати більше за потребою. І я можу додати , echoперш ніж , mvякщо я хочу , щоб візуально перевірити , що він буде робити те , що потрібно (повторіть без додаткових , echoтільки якщо все виглядає правильно:

find aaa -name \*.png -exec sh -c 'new=$(echo "{}" | tr "/" "-" | tr " " "_"); echo mv "{}" "$new"' \;

Зауважте також, що я використовую find aaa..., а не find .... або find aaa/..., оскільки останні два залишать смішні артефакти у кінцевому імені файлу.


Дякую за цей один вкладиш - це добре працювало для мене, коли я робив це поза межами каталогу (саме це ви і сказали). Коли я намагався зробити всередині каталогу (сподіваючись уникнути додавання імені кореневого каталогу), усі файли "зникли". Я перетворив їх у приховані файли в тому самому каталозі.
дгіг

2
find . -mindepth 2 -type f -name '*' |
  perl -l000ne 'print $_;  s/\//-/g; s/^\.-/.\// and print' |
    xargs -0n2 mv 

Примітка: це не буде працювати для імені файлів, які містять \n.
Це, звичайно, лише переміщує fфайли типу ...
Єдине зіткнення імен було б із файлів, які існували в pwd

Тестовано з цим базовим підмножиною

rm -fr junk
rm -f  junk*hello*

mkdir -p  junk/junkier/junkiest
touch    'hello    hello'
touch    'junk/hello    hello'
touch    'junk/junkier/hello    hello'
touch    'junk/junkier/junkiest/hello    hello'

У результаті

./hello    hello
./junk-hello    hello
./junk-junkier-hello    hello
./junk-junkier-junkiest-hello    hello

0

Ось lsверсія .. коментарі вітаються. Це працює для химерних імен файлів, як це "j1ळ\n\001\n/j2/j3/j4/\nh  h\n", але мені дуже цікаво знати про будь-які підводні камені. Це скоріше вправа в тому, "чи можуть зловмисники lsнасправді це зробити (надійно)?"

ls -AbQR1p * |                        # paths in "quoted" \escaped format
    sed -n '/":$/,${/.[^/]$/p}' |     # skip files in pwd, blank and dir/ lines
    { bwd="$PWD"; while read -n1;     # save base dir strip leading "quote
                        read -r p; do # read path
        printf -vs "${p%\"*}"         # strip trailing quote"(:)
        [[ $p == *: ]] && {           # this is a directory
            cd   "$bwd/$s"            # change into new directory
            rnp="${s//\//-}"-         # relative name prefix
        } || mv "$s" "$bwd/$rnp$s"
      done; }

-1

Просто передайте ім'я файлу + шлях до trкоманди.tr <replace> <with>

for FILE in `find aaa -name "*.png"`
do
  NEWFILE=`echo $FILE | tr '/' '-'`
  cp -v $FILE $NEWFILE
done

1
Це не обробляє дивні імена файлів безпечно. Пробіли його порушать (серед іншого).
jw013

На перший погляд, це чисте, читабельне рішення, але @ jw013 вірно, воно не справляється з просторами. Чи допоможе змінити cpлінію на cp -v "$FILE" "$NEWFILE"допомогу? (Крім того, ви не повинні приймати лише файли png.)
Натан JB

2
@ NathanJ.Brauer Додавання лапок до cpрядка недостатньо. Весь підхід до розбору findвиводу з forциклом є недосконалим. Єдині безпечні способи використання - findце -execабо, -print0якщо вони доступні (менш портативні, ніж -exec). Я б запропонував більш надійну альтернативу, але ваше питання наразі недостатньо визначено.
jw013

-2

Безпечний для пробілів у назви файлів:

#!/bin/bash

/bin/find $PWD -type f | while read FILE
do
    _new="${FILE//\//-}"
    _new="${_new:1}"
    #echo $FILE
    #echo $_new

    /bin/mv "$FILE" "$_new"
done

Минув міну, а cpне з mvочевидних причин, але повинен виробляти те саме.


3
type find-> find is /usr/bin/findна моїй машині. Використання PATHяк імені змінної у сценарії - жахлива ідея. Крім того, ваш сценарій mangles називає імена файлів з пробілами або накидами, тому що ви неправильно використовуєте readкоманду (шукайте цей сайт IFS read -r).
Жил 'SO- перестань бути злим'

find is hashed (/bin/find), відповідне як? Різні дистрибутиви різні?
Тім

Знав, що я повинен був триматися осторонь цієї принади.
Тім

@Tim Ви завжди можете видалити відповідь, щоб повернути свій представник (і мій, а також b / c коштує реп. Шляхи жорсткого кодування в сценарії, схоже, підказують, що вам не вистачає точки PATHзмінної середовища, що використовується кожною системою Unix. Якщо ви пишете, /bin/findто ваш сценарій насправді порушений на кожній системі (Gilles, шахта і, ймовірно, в ОП), яка не має /bin/find(наприклад, b / c це є /usr/bin/find). Вся суть PATHзмінної середовища полягає в тому, щоб це не було проблемою.
jw013

До речі, $(pwd)вже доступний в змінної: $PWD. Але ви не повинні використовувати абсолютний шлях тут: якщо ваш скрипт викликається, скажімо,, /home/timвін намагається перейменувати /home/tim/some/pathна /home-tim-some-path.
Жил "ТАК - перестань бути злим"
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.