эти решения, на которые вы ссылаетесь, на самом деле довольно хороши. Некоторые ответы могут не иметь объяснения, поэтому давайте разберемся, возможно, добавим еще.
эта твоя линия
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 достаточно умен, чтобы знать кавычки в $(…) парные вместе.
это можно улучшить. Примечание basename - это отдельный исполняемый файл, а не встроенная оболочка (в bash run type basename для всех type cd). Нерест любой лишний процесс обходится дорого, требует ресурсов и времени. Нерест его в петле обычно выполняет плохо. Поэтому вы должны использовать все, что предлагает вам оболочка, чтобы избежать дополнительных процессов. В этом случае решение:
… > "./${file%.txt}_sorted.txt"
синтаксис описан ниже для более общего случай.
в случае, если вы не знаете расширение:
… > "./${file%.*}_sorted.${file##*.}"
синтаксис объяснил:
${file#*.} -$file, но самая короткая строка, соответствующая *. удаляется спереди;
${file##*.} -$file, но самая длинная строка, соответствующая *. удаляется спереди; используйте его, чтобы получить только расширение;
${file%.*} -$file, но самая короткая строка, соответствующая .* удаляется из конец; используйте его, чтобы получить все, кроме расширения;
${file%%.*} -$file, но с самой длинной строкой, соответствующей .* удаляется с конца;
сопоставление шаблонов похоже на glob, а не на regex. Это значит * - подстановочный знак для нуля или более символов, ? - это замена одного персонажа (нам не нужны ? в вашем случае, хотя). Когда вы вызываете 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
Примечание. the . включен в это время. Вы можете получить неожиданные результаты, если $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
это также попытается обработать каталоги (если они есть); вы уже знаете что делать исправить.