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