эти решения, на которые вы ссылаетесь, на самом деле довольно хороши. Некоторые ответы могут не иметь объяснения, поэтому давайте разберемся, возможно, добавим еще.
эта твоя линия
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
это также попытается обработать каталоги (если они есть); вы уже знаете что делать исправить.