Эти решения, на которые вы ссылаетесь, на самом деле довольно хороши. В некоторых ответах может отсутствовать объяснение, поэтому давайте разберемся, добавим еще, может быть.
Эта ваша линия
for file in *.txt
указывает на то, что расширение известно заранее (примечание: POSIX-совместимые среды чувствительны к регистру, *.txt
не будут совпадать FOO.TXT
). В таком случае
basename -s .txt "$file"
должен вернуть имя без расширения ( basename
также удаляет путь к каталогу: /directory/path/filename
→ filename
; в вашем случае это не имеет значения, поскольку $file
не содержит такого пути). Чтобы использовать этот инструмент в вашем коде, вам нужна подстановка команды, которая выглядит, как это в целом: $(some_command)
. Подстановка команд принимает выходные данные some_command
, обрабатывает их как строку и помещает их туда, где $(…)
находится. Ваше конкретное перенаправление будет
… > "./$(basename -s .txt "$file")_sorted.txt" # ^^^^^^^^^^^^^^^^^^^^^^^^^^^ the output of basename will replace this
Вложенные кавычки в порядке, потому что Bash достаточно умен, чтобы знать, что кавычки внутри $(…)
объединены в пару.
Это можно улучшить. Note basename
- это отдельный исполняемый файл, а не встроенная оболочка (в Bash run type basename
, сравните с type cd
). Создание любого дополнительного процесса является дорогостоящим, требует ресурсов и времени. Порождение его в цикле обычно работает плохо. Поэтому вы должны использовать все, что предлагает вам оболочка, чтобы избежать лишних процессов. В этом случае решение:
… > "./$_sorted.txt"
Синтаксис объясняется ниже для более общего случая.
Если вы не знаете расширение:
… > "./$_sorted.$"
Синтаксис объяснил:
$
-$file
, но самое короткое соответствие строк*.
удаляется спереди;$
-$file
, но самая длинная строка соответствия*.
удаляется спереди; используйте его, чтобы получить только расширение;$
-$file
, но соответствие самой короткой строки.*
удаляется с конца; используйте это, чтобы получить все, кроме расширения;$
-$file
, но с самой длинной строкой совпадение.*
удаляется с конца;
Сопоставление с образцом похоже на глобус, а не на регулярное выражение. Это означает *
, что подстановочный знак для нуля или более символов, ?
это подстановочный знак для ровно одного символа ( ?
хотя в нашем случае мы не нуждаемся ). Когда вы вызываете ls *.txt
или for file in *.txt;
используете тот же механизм сопоставления с образцом. Шаблон без подстановочных знаков допускается. Мы уже использовали $
где .txt
шаблон.
Пример:
$ file=name.name2.name3.ext $ echo "$" name2.name3.ext $ echo "$" ext $ echo "$" name.name2.name3 $ echo "$" name
Но будьте осторожны:
$ file=extensionless $ echo "$" extensionless $ echo "$" extensionless $ echo "$" extensionless $ echo "$" extensionless
По этой причине может быть полезна следующая штуковина (но это не так, объяснение ниже):
$}
Он работает, идентифицируя все, кроме extension ( $
), а затем удаляет это из всей строки. Результаты таковы:
$ file=name.name2.name3.ext $ echo "$}" .ext $ file=extensionless $ echo "$}" $ # empty output above
Обратите внимание, что .
включен в этот раз. Вы можете получить неожиданные результаты, если $file
содержите литерал *
или ?
; но Windows (где расширения имеют значение) не разрешает эти символы в именах файлов в любом случае, поэтому вам может быть все равно. Однако […]
или {…}
, если имеется, может вызвать собственную схему сопоставления с образцом и сломать решение!
Ваше «улучшенное» перенаправление будет:
… > "./$_sorted$}"
Он должен поддерживать имена файлов с расширением или без расширения, хотя, к сожалению, не с квадратными или фигурными скобками. Довольно обидно. Чтобы это исправить, вам нужно заключить в двойную кавычку внутреннюю переменную.
Действительно улучшено перенаправление:
… > "./$_sorted$"}"
Двойные кавычки $
не делают шаблон! Bash достаточно умен, чтобы разделять внутренние и внешние кавычки, потому что внутренние встроены во внешний ${…}
синтаксис. Я думаю, что это правильный путь .
Другое (несовершенное) решение, давайте проанализируем его по образовательным причинам:
$
Он заменяет первый .
на _sorted.
. Это будет хорошо работать, если у вас есть не более одной точки $file
. Существует аналогичный синтаксис, $
который заменяет все точки. Насколько я знаю, нет варианта заменить только последнюю точку.
Тем не менее первоначальное решение для файлов с .
надежным внешним видом. Решение для extensionless $file
тривиально: $_sorted
. Теперь все, что нам нужно, это способ разграничить два случая. Вот:
[[ "$file" == *?.* ]]
Он возвращает состояние выхода 0 (true) тогда и только тогда, когда содержимое $file
переменной соответствует шаблону с правой стороны. Шаблон говорит: «есть точка после хотя бы одного символа» или, что то же самое, «есть точка, которой нет в начале». Суть в том, чтобы рассматривать скрытые файлы Linux (например .bashrc
) как без расширения, если где-то нет другой точки.
Обратите внимание, что нам нужно [[
здесь, а не [
. Первый более мощный, но, к сожалению, не переносимый ; последний является портативным, но слишком ограниченным для нас.
Логика теперь выглядит так:
[[ "$file" == *?.* ]] && file1="./$_sorted.$" || file1="$_sorted"
После этого, $file1
содержит желаемое имя, поэтому ваше перенаправление должно быть
… > "./$file1"
И весь фрагмент кода ( *.txt
заменен на, *
чтобы указать, что мы работаем с любым расширением или без расширения):
for file in *; do printf 'Processing %s\n' "$file" [[ "$file" == *?.* ]] && file1="./$_sorted.$" || file1="$_sorted" LC_ALL=C sort -u "$file" > "./$file1" done
Это попыталось бы также обработать каталоги (если они есть); Вы уже знаете, что нужно сделать, чтобы это исправить.