Comments 169
Вы можете написать свою статью, или хотя бы подробный комменнтарий, как это можно сделать в zsh — это будет полезно для всех.
Кстати, а можете подсказать в каких современных Линуксах не встречается баш из-коробки, если не считать ембеддед варианты?
#!/usr/bin/env ksh
запускаем — все работает и в ksh
using cut
real 0m0.70s
user 0m0.02s
sys 0m0.12s
using ##
real 0m0.01s
user 0m0.01s
sys 0m0.00s
Кстати, в ksh еще быстрее
Кстати, а можете подсказать в каких современных Линуксах не встречается баш из-коробки, если не считать ембеддед варианты?Коллега скорее всего просто напутал. bash везде есть, просто /bin/sh — не всегда линк именно на bash «из коробки». Скажем, в той же ubuntu — это линк на dash
Не знаю, можно ли считать alpine linux эмбеддед, но в нем есть только sh. Сам alpine часто используется в докер-образах.
Подозреваю, что когда проект начинался (лет 8-9 назад), докера еще не было, и то, что легковесный дистрибутив кроме embedded хорошо пойдет для контейнеров и виртуалок не подозревали.
Но как правило, bash есть на любом linux сервере или его туда можно поставить. Но ключевое тут — все же linux
и вместо того, чтобы разбираться с автокомплитом в bash, не лучше ли перейти на zsh и разбираться уже там с автокомплитом?— вот зачееем?
+1.
мало того — там где классический truncate можно было успешно использовать, вытащили модный fallocate...
Правильно будет «что требуется в конкретном данном случае».
P.S. Понятно, что и для sparse и для fallocate, это должно поддерживаться файловой системе.
Ранее упоминался multitail
хде? ))
real 0m3.442s
user 0m1.220s
sys 0m1.942s
using
real 0m5.629s
user 0m4.194s
sys 0m1.364s
Результат противоположный, плюс можно выкинуть cat, который вы совершенно зря используете. А вместо echo $STRING | cmd используйте лучше cmd <<<"$STRING".
$ cat test.sh
#!/bin/bash
STRING="Name:Date:Shell"
echo "using cut"
time for A in {1..1000}
do
cut -d ":" -f 3 > /dev/null <<<$STRING
done
echo "using ##"
time for A in {1..1000}
do
echo ${STRING##*:} > /dev/null
done
$ ./test.sh
using cut
real 0m0.936s
user 0m0.000s
sys 0m0.220s
using ##
real 0m0.010s
user 0m0.008s
sys 0m0.004s
Какая у вас ОС?
bash /tmp/test.sh
using cut
real 0m0.674s
user 0m0.034s
sys 0m0.138s
using ##
real 0m0.900s
user 0m0.666s
sys 0m0.214s
Второй
bash /tmp/test.sh
bash /tmp/test.sh
using cut
real 0m0.656s
user 0m0.021s
sys 0m0.125s
using ##
real 0m0.589s
user 0m0.426s
sys 0m0.157s
CentOS 6
Но у меня однозначный результат в Ubuntu, FreeBSD, Windows и
[saboteur@tt test]$ uname -a
Linux tt.com.ua 2.6.32-358.14.1.el6.x86_64 #1 SMP Tue Jul 16 23:51:20 UTC 2013 x86_64 x86_64 x86_64 GNU/Linux
[saboteur@tt test]$ cat /etc/redhat-release
CentOS release 6.4 (Final)
[saboteur@tt test]$ ./test.sh
using cut
real 0m1.064s
user 0m0.075s
sys 0m0.198s
using ##
real 0m0.030s
user 0m0.022s
sys 0m0.007s
Ладно еще с Мак — он не совсем Линукс.Я бы сказал, что Mac — это совсем не Linux. А bash там древний, как говно мамонта.
Причём тут Мак вообще?
«Мак» у меня.
Дюжина приемов в Linux, которые действительно сэкономят уйму времениИ? Причём тут Мак? «Вы слышали Карузо?!» – «Нет. Мне Рабинович по телефону напел».?
Если вы прочитаете инструкцию для управления современным мерседесом и сядете за руль Форд T — то вас будет ждать масса «приятных» открытий.
Userspace в Mac (как и большинстве проприетарных Unix'ов) — совершенно не радует новизной. Даже python на вышедшей месяц назад macOS High Sierra — двухгодичной давности, а bash — так и вовсе 3.2.57 (притом что bash 4 вышел почти 8 лет назад).
Зачем мне отвечать не на мои слова?
просто ставить надо все из homebrew
GNU bash, version 4.4.12(1)-release (x86_64-apple-darwin16.3.0)
За первый пункт отдельное спасибо.
HISTCONTROL и HISTIGNORE
. В CentOS Minimal это по умолчанию не сделано.К сожалению, местами ваша статья тоже учит людей плохому, как и многие другие статьи о шелл-программировании.
КДПВ. То, что там написано, не поддаётся никакой критике:
ls -d * | sort -r | tr "\n" " "; echo
Кто-то так в жизни пишет скрипты? А что если:
- файлов слишком много и
*
раскрывается в слишком длинную последовательность, которая не поместится в лимит командной строки? - где-то в имени файла затесался перевод строки (как бы ужасно это ни звучало)?
Более правильно так:
# Если на вывод будет смотреть человек.
ls --reverse
# Если это зачем-то нужно сделать в скрипте.
find -maxdepth 1 ! -path . -print0 | sort -z -r | xargs -0 echo
Разбиение строки. Как быть в случае, когда нужны все поля, или какое-то поле из середины? В этом случае ${##}
не поможет, но вполне можно разбить строку в массив средствами Bash:
userInfoString="username:homedir:shell"
IFS=':'
userInfo=($userInfoString)
echo "${userInfo[0]}"
echo "${userInfo[1]}"
echo "${userInfo[2]}"
# Восстановим дефолтное значение разделителя. По-хорошему,
# следовало бы сохранить старое значение IFS, а затем восстановить.
unset IFS
Последовательности. В приведённом простом примере совсем не обязательно использовать seq
, достаточно добавить ведущих нулей в оператор {..}
. Не стоит также забывать про кавычки, хотя тут их отсутствие не несёт угрозы.
# Было.
for srv in `seq -w 1 10`; do echo server${srv};done
# Стало.
for srv in {01..10}; do echo "server${srv}"; done
Кавычки!. Не знаю, почему, но вы почти нигде не квотируете строки. Как насчёт файлов с пробелами в имени?
# Было.
for file in *.txt; do name=`basename $file .txt`;mv $name{.txt,.lst}; done
# Стало.
for file in *.txt; do name="$(basename "$file" .txt)"; mv "$name"{.txt,.lst}; done
Коварная команда test
. Увидел у вас вот такой фрагмент кода:
[ "$REPLY" = 'n' ]
Что будет, если пользователь введёт '--help' или ещё что-то, начинающееся с дефиса? В баше (по крайней мере, новых версиях) не произойдёт ничего страшного, но в древних шеллах команда test может перепутать операнд с опцией и выдать сообщение об ошибке. Думаю, лучше так:
# Если пишем на башике и на другие шеллы не смотрим.
# (Заодно можем опустить кавычки.)
[[ $REPLY == 'n' ]]
# Если хотим, чтобы работало как можно более везде, придётся сделать
# подношение древним богам портабельности.
[ x"$REPLY" = x'n' ]
Надеюсь, что вы действительно прочитали вводную в статье, а именно вот это:
2. Разбиение строки — я же прямо и пишу «часто используют cut или даже awk, чтобы просто получить значение какого-то одного столбца. Но в простых случаях более чем достаточно...».
Если случай более сложный, есть множество и других приемов. Спасибо, что вы упомянули.
Коварная команда test. Увидел у вас вот такой фрагмент кода:
[ "$REPLY" = 'n' ]
Что будет, если пользователь введёт '--help'
В данном случае все корректно. Пользователь не может ввести --help, потому что в примере в статье мы используем REPLY — как результат от команды «read -n 1»
Кстати, использование [[ ]] — это гораздо больше башизм, чем стандартный test, я стараюсь его избегать.
Кавычки использовать нужно и полезно, и если вы почитаете мою другую статью, где я указываю частые ошибки, связанные с wildcards, вы поймете, что не обязательно в КАЖДОМ примере, в котором вы хотите объяснить что-то одно, использовать всевозможные защиты от несуществующих в данном примере проблем — текст так перегружается, и уловить основную суть в нем не выходит. Вот Вы, например, неуловили откуда взялась REPLY, видимо нужно пример еще упростить для наглядности.
Кавычки использовать нужно и полезно, и если вы почитаете мою другую статью, где я указываю частые ошибки, связанные с wildcards, вы поймете, что не обязательно в КАЖДОМ примере, в котором вы хотите объяснить что-то одно, использовать всевозможные защиты от несуществующих в данном примере проблем — текст так перегружается, и уловить основную суть в нем не выходит.Извините, но это — категорическая неправда. В примерах как раз — лучше защищаться от всех бед, какие только могут стрястись. Да, есть опасность, что ученик «не увидит за деревьями леса», но зато предотвращается куда более серьёзная проблема — заучивание неверных парадигм. Потому что в вашем упрощённом примере:
...
runapplication --user=user --password=$PASSWORD
вы научили людей двум вещам:1. Пароль лучше класть в отдельный файл
2. При использовании данных из этого файла можно ссылаться на переменную как $PASSWORD — без забот и кавычек
И никакие ссылки на другие статьи не помогут. Раз в примере так написано — значит так и надо делать!
Вот Вы, например, неуловили откуда взялась REPLY, видимо нужно пример еще упростить для наглядности.Нет, не нужно. Нужно просто всегда следовать «правилам хорошего тона».
Простите, вы действительно сейчас взяли и раскритиковали вводный скриншот к статье?
Ну да, взял и раскритиковал. Вы же эту команду не специально в картинку спрятали, чтобы её было сложнее раскритиковать?
В реале я использую множество приемов, [..], что может попасть под NDA, поэтому я не мог копи-пастить и специально переписал и максимально упростил все примеры в статье.
Вы хотите сказать, что забесплатно переменные можно не квотировать?
Если от упрощения примеров они становятся более хрупкими и менее корректными — не надо так упрощать. Программировать на шелл-языках сложно — пожалуйста, не внушайте людям, что это легко.
Я же прямо и пишу «часто используют cut или даже awk, чтобы просто получить значение какого-то одного столбца.
Окей, какого-то одного, но не обязательно же последнего? Давайте вытащим средний элемент: в примере с cut
нужно будет поменять одну цифру, с ${##}
уже придётся извратиться.
Пользователь не может ввести --help, потому что в примере в статье мы используем REPLY — как результат от команды «read -n 1»
Да, с --help
я недоглядел. Но пользователь всё ещё может ввести просто дефис.
Кстати, использование [[ ]] — это гораздо больше башизм, чем стандартный test, я стараюсь его избегать.
Каждый, конечно, волен сам выбирать свою судьбу, но, как мне кажется, лучше уж честный скрипт на Bash (которому бонусом доступны массивы, хеш-таблицы, $(...)
и регулярные выражения) с минимальным использованием внешних утилит, чем "портабельный" скрипт на *sh, который на эту самую портабельность никем никогда не проверялся.
вы поймете, что не обязательно в КАЖДОМ примере, в котором вы хотите объяснить что-то одно, использовать всевозможные защиты от несуществующих в данном примере проблем
Нельзя по-отдельности объяснить составляющие концепции и ожидать, что они сами сольются в монолитное знание. В каждом примере обязательно нужно использовать всевозможные защиты от якобы несущественных проблем, либо говорить "Этот пример работает неправильно в таких-то случаях". В противном случае получается, что вы именно что "учите плохому".
Вы хотите сказать, что забесплатно переменные можно не квотировать?
Почему же нельзя не квотировать переменные в примере, типа:
$ STRING="username:homedir:shell"
$ echo "$STRING"|cut -d ":" -f 3
Переменная как-то не так сработает в этом примере?
Да, с --help я недоглядел. Но пользователь всё ещё может ввести просто дефис.
Давайте еще раз посмотрим на код:
until [ "$REPLY" = «y» ]; do
# executing some command
read «Press 'y' to continue or 'n' to break, any other key to repeat this step» -n 1
if [ "$REPLY" = 'n' ]; then exit 1; fi
done
И что же случится, если пользователь вдруг введет '-'? Случится то, что он нажмет «any other key». Я не вижу тут никакой катастрофы — все предусмотрено.
Нельзя по-отдельности объяснить составляющие концепции и ожидать, что они сами сольются в монолитное знание. В каждом примере обязательно нужно использовать всевозможные защиты от якобы несущественных проблем, либо говорить «Этот пример работает неправильно в таких-то случаях». В противном случае получается, что вы именно что «учите плохому».
Я считаю, что это невозможно в принципе, либо по такому пути сможет обучаться один из миллиона, обладающий эйдетической или как там она памятью, и то не уверен что такое возможно. И это не означает «учить плохому». Всегда есть определяющие акценты на важные моменты.
Можно недалеко ходить на гиктаймс, где есть множество статей про физику, которые выглядяит примерно так:
«Очевидно, что 2+2=4, поэтому при рассмотрении взаимодействия бозона хиггса и остальных частиц, мы видим, что „и тут идет зубодробительные формулы и термины, из которых с трудом понимаешь процентов 5“.
Поэтому в этом вопросе я останусь при своем мнении — примеры должны быть максимально просты, и доносить свою основную мысль, не отвлекаясь на дополнительные вещи, которые не касаются описываемого функционала.
Почему же нельзя не квотировать переменные в примере, типа
$ STRING="username:homedir:shell" $ echo "$STRING"|cut -d ":" -f 3
Прежде всего, в качестве примера отсутствующего квотирования я приводил совсем другой фрагмент кода. Но и этот не без недостатков:
- команда
echo
может иметь параметры, поэтому еслиSTRING
начинается с дефиса — все может пойти не так, как задумывалось; - без кавычек в
echo "$STRING"
у вас любое количество идущих подряд пробелов станет одним пробелом.
Можете мне сколько угодно говорить, что это всё глупости и просто не нужно иметь на машине имен пользователей и файловых путей с пробелами, вот только не всегда входные данные приходят в корректном виде. Их надо фильтровать и вообще обращаться с ними трепетно.
Случится то, что он нажмет «any other key». Я не вижу тут никакой катастрофы — все предусмотрено.
Вы уверены, что "test - = n
" на всякой машине (в том числе в FreeBSD, OpenSolaris, QNX) отработает именно так, как у вас? Некоторые варианты test
вполне могут воспринять дефис как окончание списка флагов и выдать сообщение об ошибке. Я вам не найду конкретную версию системы, где на этот баг можно нарваться, но я не с потолка это взял, а подсмотрел в ./configure
-скриптах. Что бы ни говорили про Autotools, но её авторы понимали в портируемости кода.
Поэтому в этом вопросе я останусь при своем мнении — примеры должны быть максимально просты, и доносить свою основную мысль, не отвлекаясь на дополнительные вещи, которые не касаются описываемого функционала.
Согласен: примеры должны быть просты и доносить основную мысль, но также приучать людей к тому, что такие вещи как квотирование переменных должны "соскальзывать с кончиков пальцев". По-большому счёту, с этих несчастных кавычек и начинается понимание шелл-скриптинга.
1. команда echo может иметь параметры, поэтому если STRING начинается с дефиса — все может пойти не так, как задумывалось;
2. без кавычек в echo "$STRING" у вас любое количество идущих подряд пробелов станет одним пробелом.
Простите, но ОТКУДА в моем примере дефисы? Вы же видите скрипт целиком? В нем в STRING задается с одним конкретным значением, в котором есть все необходимое для демонстрации, как работает конкретная команда.
Ни дефисов ни подряд идущих пробелов там нет. Скрипт рабочий как минимум на всех доступных мне операционкам.
Я понимаю, если в скрипте допущена ошибка, но IMHO вы сейчас придираетесь к несуществующим выдумкам. Я понимаю что такое перфекционизм, но в данном случае он явно излишен.
Простите, но ОТКУДА в моем примере дефисы? Вы же видите скрипт целиком? В нем в STRING задается с одним конкретным значением, в котором есть все необходимое для демонстрации, как работает конкретная команда.
Если входные данные подразумеваются неизменными, то просто напишите вместо этого всего скрипта echo "shell"
— зачем же так сложно, как у вас, делать? Или всё-таки, сущность примера состоит в том, что входные данные можно варьировать без потери работоспособности?
К тому же, основные претензии были вот к этим двум командам:
// 1
runapplication --user=user --password=$PASSWORD
// 2
for file in *.txt; do name=`basename $file .txt`;mv $name{.txt,.lst}; done
Я вижу, что вы отредактировали статью и добавили кавычки.
О чём мы тогда спорим-то?
Если хотите, посмотрите пожалуйста мою другую статью — там как раз есть парочка примеров посложнее, мне будет приятно, если вы подскажете упущенные моменты.
Ну, вообще есть байка про то, как сдавали софт каким-то американским воякам, и именно такой подход привёл к появлению надписи «Press any white key». Иначе принимающий генерал жал Shift и Alt и говорил, что программа не работает. (Что они делали с этим, когда начали массово делать клавиатуры без цветового выделения групп клавиш — не знаю. Наверно, предложили просто жать пробел. Но байка осталась.)
И я отношусь к фичам шелла типа автораскрытия параметров с таким подозрением, что лучше перестрахуюсь в триста слоёв проверками типа if test «x$foo» = «xbar»; несмотря на 200% уверенность, что в foo не будет ни '-' ни '=' вначале. Потому что лучше чуть подуть на воду, чем потерять данные или свести с ума систему. А учить всегда лучше хорошему :)
А не проще ли будет переписать скрипт на питон?
Кода конечно будет больше, но таких проблем (магия баша) нет.
Возможно, что работать быстрее даже будет.
Другой момент тут, что в Unix традиционно роль «шелла с человеческим лицом» была отдана немного другим средствам — Tcl и Perl; Tcl достаточно быстро потерял популярность, но Perl продолжает активно использоваться в таких традициях. Трёхслойный бутерброд типа «C внизу, Perl и шелл» типичен в Unix мире (например, Git, Fidogate), а вот для Python такие случаи сильно реже.
Если перед командой поставить пробел, она не попадет в bash history
Если не ошибаюсь, это только если в настройках значится
export HISTCONTROL=ignoreboth
Служба безопасности не лазит по инету и не выискивает конкретный участок кода — это задача нереальная. Зато вот взять ключевые слова, в которые как раз и входят названия серверов, домена, приложений, опций — за что можно зацепиться, и гуглится по популярным ресурсам. Найдут — будут разбираться, и масштаб разборок может оказаться совершенно несопоставимым с реальной угрозой. Просто потому, что в вопросе могут быть замешаны деньги и политика.
Давайте приведу потенциальный пример, как могут развиваться события:
Разработчик работает в аутсорсе. Устраивается в проект, подписывает NDA, по которому обязуется не выкладывать из того, что пишется в проекте. Однажды выкладывает на публичный ресурс кусок текста, с названием внутреннего сервера.
Реальной технической угрозы — никакой.
Но подпадает под NDA? Подпадает.
Служба безопасности компании-заказчика это однажды находит, передает специалистам.
Специалисты смотрят — ну вроде как технически не страшно, передают дело юристам со своим описанием.
Юристы смотрят ага, нарушение NDA, можем вкатить компании-исполнителю штраф в 1-5%, согласно нашему договору.
1% от ОБЩЕГО договора между компаниями — может составлять несколько годовых зарплат менеджера, а то и всего проекта — зависит от размера компаний, поэтому просто так такие случаи бесследно проходить не будут.
Итого — зачем рисковать? =)
Но я действительно совершенно не хочу спорить об этом с юристами =)
Поэтому проще переписать с нуля, вдобавок проверить, что пример рабочий, а не требует конкретных имен файлов/серверов/юзеров для выполнения — ведь неприятно, когда копируешь кусок кода из статьи к себе, чтобы проверить — а он просто не выполняется.
$ echo ${STRING##:*}
shell
Нужно:
echo ${STRING##*:}
Потому обычно используются for циклы, сначала с echo префиксом. А башевские встроенные регекспы классная вещь, но иногда срабатывает не совсем так, как ожидает уставший мозг.
Пример про сохранение части лога в переменной(памяти) вообще порадовал.
Вместо последнего примера в (8) рекомендую man rename
:
rename .htm .html *.htm
runapplication --user=user --password=$PASSWORD
Вероятно, здесь стоит упомянуть, что передача пароля в аргументе командной строки — вообще не очень хорошая идея, поскольку он будет светиться в /proc/$pid/cmdline
Если не можете выиграть битву, поменяйте поле:
#!/usr/bin/env bash
# prepare data
for A in {1..1000}; do
STRING+="Name$RANDOM Date$RANDOM Shell$RANDOM"$'\n'
done
echo "using cut"
time cut -d " " -f 3 <<<"$STRING" > /dev/null
echo "using shell"
time while read a b c; do
echo $c
done <<<"$STRING" > /dev/null
# using cut
# real 0.006
# у меня медленный компьютер, результат с echo аналогичен тому, что предлагает автор
# using shell
# real 0.026
- Когда нужно обработать много данных — пайпы лучше.
- Единичные случаи — да, эффективнее использовать bash-измы. Обратная совместимость пострадает, но мы вас предупреждали.
- С другой стороны, измерять эти миллисекунды имеет смысл, только когда данных много, следовательно см. п. 1.
В последнее время cut работает хорошо. Не так давно в GNU coreutils cut работал так, что его обгонял awk.
#!/usr/bin/env ksh
# prepare data
for A in {1..1000}; do
STRING+=«Name$RANDOM Date$RANDOM Shell$RANDOM»$'\n'
done
echo «using cut»
time for A in {1..1000}
do
cut -d ":" -f 3 > /dev/null <<<$STRING
done
echo «using ##»
time while read
do
echo ${REPLY##*:}
done <<<"$STRING" > /dev/null
echo «using read»
time while read a b c
do
echo $c
done <<<"$STRING" > /dev/null
saboteur@ubuntu:~/test$ ./test.sh
using cut
real 0m0.84s
user 0m0.04s
sys 0m0.16s
using ##
real 0m0.00s
user 0m0.01s
sys 0m0.00s
using read
real 0m0.00s
user 0m0.00s
sys 0m0.00s
Дело в подготовке данных (вы вызываете cut 1000 раз, а я один раз но для 1000 строк).
Правда я в уме держал реальный пример — чтение из разных конфигов, где я считываю значение переменных, и я видимо подсознательно не смог их все запихнуть в один файл/поток.
чтение из разных конфигов, где я считываю значение переменных
Если позволяет формат конфигов — то не знаю ничего быстрее чем source config.cfg
, всё что было внутри конфига превратится в переменные баша. Такой способ используется повсеместно в initscripts. Все эти ifcfg-eth0 в RHEL по крайней мере — по сути шелл-скрипты.
Ещё один способ, который годится для перегона нестандартного формата конфига — eval "$(sed ... config.ini)"
. Сед приводит конфиг в башеподобному виду, eval превращает в переменные.
Обычно так делают студенты домашку, так как им показали ограниченный набор базовых команд.
VAR1=$(cat file.cfg | grep var | cut -f2 | sed 's/"//')
… и так десять раз
А как же find
? Много бывает полезных применений. Совсем недавно нужно было посчитать количество файлов на уровне каждой директории рекурсивно от текущей:
find -type f -printf "%h\n" | sort | uniq -c | sort -n
Полезно будет сделать алиас, который получает PID вашего приложения из PID файла, и автоматически завершит tail при завершении процесса
А не полезней ли запускать tail -F
, чтобы он продолжал следить за логом приложения и после его перезапуска или ротации лога?
На файловых системах, которые поддерживают аллокацию места (xfs, ext4, Btrfs...), данная команда будет выполнена мгновенно, в отличие от dd.
Пффф! Вот тоже мнгновенно:
dd if=/dev/zero of=path/to/filename.txt bs=1 count=0 seek=1G
mkswap path/to/filename.txt
swapon path/to/filename.txt
У меня такое впечатление, что статью писали 2 человека, и/или второпях. Немного "секретов" bash, немного coreutils, совсем немного openssl. Утверждение про экономию времени очень спорное.
$ echo "${STRING##*:}"
shell
Это не разбиение строки. Это Remove matching prefix pattern (ц) man bash.
создание файла нужного размера — это операция, занимающая много времени? Не уверен, что эта задача насколько востребована, что ее оптимизация сэкономит больше времени, чем потрачено на чтение комментариев.
Урезание логов — готовый антипаттерн, как он относится к экономии времени?
Sensitive data — шифрование с помощью openssl не экономит время. И еще я где-то читал, что плохо организованное шифрование не улучгает, а ухудшает безопасность.
создание файла нужного размера — это операция, занимающая много времени? Не уверен, что эта задача насколько востребована, что ее оптимизация сэкономит больше времени, чем потрачено на чтение комментариев.Похоже вам никогда не требовалось «на лету» добавлять в систему swap. Аллокация файла на пару гиг может занять на сильно загруженной система несколько минут — за которые «слишком большой» процесс успешно займёт весь свап и система «станет колом».
Так что тут уже разница не между «выиграли несколько секунд или проиграли», а между «смогли обойтись без остановки всей системы или нет».
Не приходилось ;)
Если задача ставится именно так: "добавить_руками_прямо_сейчас_своп_на_очень_загруженной_системе_иначе_беда" — это рулетка, либо повезет, либо нет. Система уже скорее мертва, чем жива. Процессу (процессам) не хватило памяти, они начали писать в swap, все в ожидании Input/Output. Если такая ситуация наступила — ваша система плохо сконфигурирована, и все равно очень скоро будете либо добавлять память, либо настраивать (и перезагружать).
Для демонстрации работы fdisk в учебном классе, выдавать рутовые права не нужно, ибо чревато.
Но для каждого ученика мы создадим по файлику, размером пару гигабайт, внутри которых они будут играться с разделами.
Задачка простейшая, но зачем ждать несколько минут на каждый созданный гигабайт, если это делается за секунды?
Хм. С такой задачей IRL, наверное, сталкиваются чуть реже чем никогда. В 21 веке, зная про виртуализацию, не выдать каждому студенту по песочнице ?
1. Даже со всеми заготовленными образами и автоматическим разворачиванием, на 30 студентов разворачивать полноценные виртуалки — уже нужны не каждый сервер потянет просто их запустить.
2. Во время лекции, совершенно не будет времени сидеть и восстанавливать Линукс (даже из образа), если студент неудачно выполнил fdisk/mkfs/rm -rf.
3. Разворачивать каждому по своей индивидуальной виртуалке для 15-минутной темы, или просто сделать 30 файлов — неужели вы берете с собой зерноуборочный комбайн, если нужно срезать букетик полевых цветов?
Мне кажется, что всегда следует писать $(...) вместо ``. $() могут вкладываться друг в друга рекурсивно.
А ещё легче читать, потому что понятно, где начало, где конец, в отличии от `, которая выглядит симметрично.
Это больше стилистический момент, поскольку официально — оба метода равноправны, и `` не является deprecated или obsolete.
Лично мне проще читать ``, поскольку $ у меня связано больше с переменными, а $(()) с вычислениями.
Executables must start with #!/bin/bash
Вроде как сейчас комфортнее писать #!/usr/bin/env bash для максимальной совместимости?
If you are writing a script that is more than 100 lines long, you should probably be writing it in Python instead
Тут все очень зависит. У меня есть простой скрипт, который деплоит приложение, состоящее из нескольких частей. Скрипт гораздо больше, чем 100 строк, но он именно удобная обертка для различных управляющих команд. Да, тут стоит probably, но все равно…
Executables should have no extension (strongly preferred) or a .sh extension. Libraries must have a .sh extension and should not be executable.
Если у скрипта не стоит расширение, то во многих редакторах автоматически не включится подсветка. Я предпочитаю ставить расширение всегда, иначе есть проблемы с запуском подобных скриптов под той же виндой, где нет бита executable, и поведение ОС зависит именно от расширения. То есть тут strongly disagree.
SUID and SGID are forbidden on shell scripts.
Это настолько forbidden, что на многих линуксах, по дефолту SUID/SGID просто не работает на скриптах, поэтому даже особого смысла упоминать нет.
Case statement
Рекомендует использовать lower_case с подчерком для имен переменных, оставив uppercase только для environment переменных.
Не знаю, общемировая практика — использовать uppercase в шелл-переменных всегда. Код действительно будет более читабелен, особенно учитывая множество консольных команд и функций, которые идут в lowercase.
[[… ]] is preferred over [, test and /usr/bin/[.
Я пока что предпочитаю все проверить, использовать кавычки, но остаться с более совместимым [, чем использовать [[.
Но тут возможно я уже устарел…
If you've got functions, put them all together near the top of the file
Несколько странный совет. Учитывая, что шелл — это не компилируемый язык, расположить функции где-либо еще сложно, ибо их нужно объявить до того, как использовать. Но если функций много, их лучше вынести в отдельный файл, и делать обычный инклюд через source — читабельность значительно повышается, и можно использовать одну функцию в разных скриптах тоже полезно.
В общем достаточно много не понравилось…
Хм, почитал. Не могу сказать, что я готов следовать всем рекомендациям по ссылке.Некоторые их них специфичны для Гугла. Мы тоже не всё на 100% соблюдаем. По похожим причинам.
Вроде как сейчас комфортнее писать #!/usr/bin/env bash для максимальной совместимости?Если вы хотите поддерживать клоны BSD — то да. Но у Гугла нет такой задачи.
Я предпочитаю ставить расширение всегда, иначе есть проблемы с запуском подобных скриптов под той же виндой, где нет бита executable, и поведение ОС зависит именно от расширения.Ну это, как бы, правилами тоже разрешено.
Не знаю, общемировая практика — использовать uppercase в шелл-переменных всегда.Не знаю чего там говорит «общемировая практика», но во всех autofconf-скриптах, которые я видел полно «локальных» переменных типа as_myself, as_bourne_compatible и т.п., которые только внутри скрипта используются. А переменные записанные большими буквами — экспортируются и импортируются. Это не Гугл придумал.
Код действительно будет более читабелен, особенно учитывая множество консольных команд и функций, которые идут в lowercase.Перед ними знак доллара не стоит, так что спутать тяжело.
Я пока что предпочитаю все проверить, использовать кавычки, но остаться с более совместимым [, чем использовать [[Подход Гугла во многом честнее: написание переносимых скриптов — это отдельное искусство, так что если у вас нет специальной задачи сделать скрипт, который будет работать «на любом утюге», лучше использовать все возможности bash'а, чем мучиться — и всё равно получить нечто, что на FreeBSD не работает.
Несколько странный совет. Учитывая, что шелл — это не компилируемый язык, расположить функции где-либо еще сложно, ибо их нужно объявить до того, как использовать.Да, но можно завести пару функций — и поиспользовать из. Завести ещё несколько — и сделать ещё что-то. А потом, в конце — уже основной скрипт. Если нарушить одно из первых правил и напичать большой скрипт — удивление читателя гаранитровано!
В общем достаточно много не понравилось…Если не собираетесь конрибудить свои скрипты в Хром или Андрод — можете использовать свои правила.
Но если работаете в большом проекте — что-то подобное придётся составить, потому что иначе люди элементарные ошибки будут постоянно делать. А то насоздаёт кто-нибудь переменных из функции — а ты потом сиди, ломай голову, почему вызов двух функций в оном порядке работает, а если переставить их местами — уже нет.
Основная цель у меня была — совместимость скрипта под bash и ksh, которые были стандартом в местах, где работал. Но вроде как отличия минимальные (помню только какие-то delayed evaluation в ksh, но их использовать себе дороже). А я под эту цель часто пытался запихнуть ненужной совместимости с чем-то еще.
Расширение — только для того чтобы отдать файл правильной программе, программа должна смотреть на контент.
Я вас перецитирую:
Выкиньте эту операционную систему (windows, я так понимаю?), не издевайтесь над собой — ее писали криворуки.
Расширение вообще не нужно, опреациона должна сама смотреть на контент/заголовок, как это делается в unix/linux.
P.S. Скрипт, особенно библиотека, может вообще не содержать никакого #!, как тогда будет вести себя ваш умный редактор? Анализировать весь файл, и открываться полчаса, чтобы мне в скрипте поправить через vi один символ?
Не нужно обзывать кого-то криворуким за то, что он использует популярный способ, тем более, что вы сейчас наступили одновременно на мой любимый FAR и vi сразу.
Собственно, гадание по магическим байтам с помощью libmagic — это красиво. Но гадание по расширению быстрее.
В линукс — операционка вообще не смотрит на расширение. Она смотрит на права доступа и на заголовок файла внутри.
Я совершенно не собираюсь выкидывать редактор, который служит верой и правдой десятки лет только потому, что вы считаете что он не должен зависеть от расширения. При этом мне это совершенно не важно, а вот все остальное, что этот редактор умеет — мне очень важно. Поэтому ваш совет — плохой, не несет никакого полезного конструктива.
Это совершенно понятно, что определенные программы знают названия определенных файлов, так как ssh знает про ~/.ssh/id_rsa, или make знает про Makefile.
Но что именно файловый менеджер будет делать с файлом типа Makefile, например? Компилировать? чем? Компиляторов может быть много.
Что же касается text/x-makefile и text/plain — это вообще не имеет отношение к Линукс или к Windows, это имеет отношение MIME type/content type/media type, что имеет смысл в http/smtp/других способах передачи данных в интернет, список типов стандартизирует IANA. Но это никак не относится к тому, как ОС решает, что делать с файлом при попытке его открыть.
p.s. «Смотрит. Только не операционка, а файловые менеджеры, и не на расширение, а вообще»
Как в анекдоте — «Правда ли, что шахматист Петросян выиграл в лотерею тысячу рублей»?
p.p.s. Зачем вы упоминаете карму я вообще не понимаю.
Можно вместо grep в єтом случае использовать egrep, например
tail -f application.log | egrep «error|warning|failure»
Хотя да, разница небольшая.
Второй вариант не запускает еще один процесс cut, и вообще не использует пайпы, что должно работать гораздо быстрее.стоит так же учитывать, что второй вариант менее универсален. И часто вы упираетесь в производительность в bash скриптах?
Лично мне запомнить синтаксис bash variable expansions намного сложнее, чем cut/awk. Так как для меня совсем не интуитивно, что команда ${STRING##*:} извлечет 3й столбец
Не забывайте использовать -P, так как по умолчанию grep использует basic regular expression, а не PCRE, и вы можете столкнуться с тем, что просто так группы не работают.только надо учитывать, что флаг -P доступен далеко не везде. Например в том же alpine его нет
USER=`echo "$STRING"|cut -d " " -f 1`форма `some command` уже давно как deprecated и вместо нее советуют использовать $(some command)
P.S.
если кто не сталкивался, то по bash есть довольно таки хороший ресурс — wiki.bash-hackers.org/syntax/pe Так сказать краткая вижимка известного Advanced Bash-Scripting Guide
стоит так же учитывать, что второй вариант менее универсален. И часто вы упираетесь в производительность в bash скриптах?
Вот прямо сейчас сильно уперся в том, что тот же баш-скрипт под Windows работает в несколько десятков раз медленнее, что занимает несколько минут для обработки жалких 10 файлов, общим размером в 10 кбайт. Да и даже под Линуксом, он выполняется не мгновенно. После перехода на удаление по маске — секунды.
Лично мне запомнить синтаксис bash variable expansions намного сложнее, чем cut/awk. Так как для меня совсем не интуитивно, что команда ${STRING##*:} извлечет 3й столбец
Ну он не извлечет третий столбец, это относится именно к примеру из статьи. Наверное сейчас немного перепишу этот абзац, много замечаний к формулировке. С другой стороны — скрипт я пишу один раз, могу и посидеть в гугле лишних 10 минут, зато потом выполнять я его буду сотни и сотни раз.
форма `some command` уже давно как deprecated
Вот с этим я не согласен. Официально она никак не deprecated и не obsolete.
Отличается от $() только возможностью вложенности без лишнего экранирования, и стилистикой.
Ну или приведите официальный источник, где `` указывается как deprecated — я не нашел…
Вот прямо сейчас сильно уперся в том, что тот же баш-скрипт под Windows работает в несколько десятков раз медленнее, что занимает несколько минут для обработки жалких 10 файлов, общим размером в 10 кбайт.думаю это уже специфика реализации bash под windows. А запускаете в cygwin или WSL?
Ну или приведите официальный источник, где `` указывается как deprecated — я не нашел…Вот тут — pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_xcu_chap02.html#tag_23_02_06_03
они конечно не написали дословно deprecated, но тем не менее явно рекомендуют не использовать `` — «Because of these inconsistent behaviors, the backquoted variety of command substitution is not recommended for new applications».
Вообще мне понравился один комментарий на SO насчет этого момента
POSIX may not say the word deprecated but it does say «the backquoted variety of command substitution is not recommended» which is just a long winded way of saying deprecated
Так же в некоторых специфичных ситуациях поведение будет отличаться, например
$ echo "`echo \"test\" `"
test
$ echo "$(echo \"test\" )"
"test"
Да, в такой рекомендации оно понятно. Проще использовать $(), чем разбираться с экранированием и разборкой специфичных случаев. Приму к практике.
По поводу echo — поведение и должно отличаться согласно синтаксису:
When the old-style backquote form of substitution is used, backslash
retains its literal meaning except when followed by $, `, or \. The
first backquote not preceded by a backslash terminates the command sub‐
stitution. When using the $(command) form, all characters between the
parentheses make up the command; none are treated specially.
Поэтому в ``, обратный слеш нужно дублировать:
echo "`echo \\"test\\" `"
"test"
cygwin, который идет вместе с gitесли будет возможность, было бы классно запустить через WSL(я так понимаю нужна Win 10 или Win 2k16) и добавить результаты в статью.
По поводу echo — поведение и должно отличаться согласно синтаксису:да, я в курсе. Просто указал на момент, что не всегда можно просто заменить `` на $(). Это тоже стоит учитывать.
echo "${STRING##*:}" > /dev/null
real 0m0.074s
user 0m0.016s
sys 0m0.063s
cut -d ":" -f 3 > /dev/null <<<"$STRING"
real 0m8.622s
user 0m0.375s
sys 0m8.125s
echo "$STRING" | cut -d ":" -f 3 > /dev/null
real 0m27.734s
user 0m0.219s
sys 0m28.047s
c пайпами на windows все очень печально
Например безопасный ssh не имеет опции, которая позволяет передать ему пароль в командной строке. А небезопасный wget — поддерживает опцию --password.
Как раз таки разрабочики ssh позаботились о том чтобы неискушённый пользователь не мог показать пароль всем процессам в системе. А разработчик sshpass ликвидировал это досадное недоразумение:
$ sshpass 'смотрите на мой пароль в списке процессов пожалуйста' ssh root@my-super-server.com
Между тем для правильного приготовления ssh придуманы ключи.
for srv in 1 2 3; do echo "server${srv}";done
Результат в статье подправьте.
Должно выводиться
server1
server2
server3
$ ./test
using cut
real 0m1,099s
user 0m0,579s
sys 0m1,338s
using ##
real 0m2,246s
user 0m1,739s
sys 0m0,505s
$
Плиз, можете описать ОС и где она запускается? Вы не первый с подобным результатом, но я уже перепробовал на всех доступных мне вариантах ОС (около 4 линуксов, 2 юникса и bash for windows — у меня cut в десятки раз медленнее работает).
А для FreeBSD я уже приводил свои результаты — у меня показывает, что cut дольше в десятки раз.
Но у меня может быть не родной bash, я мог впихнуть туда через homebrew GNU версию
use cut
real 0m3.738s
user 0m1.661s
sys 0m1.237s
use ##
real 0m4.127s
user 0m3.067s
sys 0m1.037s
Mac OS Sierra 10.12.6 (16G29), MacBook Air (13-inch, Early 2014)
$ cat /proc/cpuinfo
…
vendor_id: GenuineIntel
cpu family: 6
model: 23
model name: Intel® Core(TM)2 Quad CPU Q9550 @ 2.83GHz
stepping: 10
microcode: 0xa0b
cpu MHz: 2833.279
cache size: 6144 KB
physical id: 0
siblings: 4
core id: 3
cpu cores: 4
apicid: 3
initial apicid: 3
fpu: yes
fpu_exception: yes
cpuid level: 13
wp: yes
flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx lm constant_tsc arch_perfmon pebs bts rep_good nopl cpuid aperfmperf pni dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm sse4_1 xsave lahf_lm tpr_shadow vnmi flexpriority dtherm
bugs:
bogomips: 5665.79
clflush size: 64
cache_alignment: 64
address sizes: 36 bits physical, 48 bits virtual
…
В полуоси был двухпанельник fc, так вот из под него можно было по нескольким первым буквам вызвать из истории все команды, которые начинались с этих букв, и выбрать из них нужную. Например, набрав «tra» и стрелочку (емнип), получим вывод типа
1. tracepath a.b.c.d
2. tracepath b.c.d.e
3. translate
4. tracepath c.d.e.f
После чего набираем число и ентер. Круто экономит мозги на предмет хранения ключей и опций.
В bash такое есть?
1. Попробуйте Ctrl+R, с поиском по командам и так далее
2. История команд есть в двухпанельнике mc.
3. А на винде, я вообще могу прямо из bash запустить например двухпанельник FAR, и там будет очень много «такого» =)
github.com/elfmz/far2l
Насчет виндовса не знаю — я ее с выхода варп-коннекта как повседневную среду не использовал — не было таких задач, которые нельзя было порешать на полуоси, а потом и в линуксе. А вот задач, котороые нельзя было порешать в виндах или их решение было сопряжено с геморроем, было много.
При включении опции просмотра истории, при наборе любой команды, автоматом предлагается автодополнение из истории, вот я ввожу «gi» и получаю:

А вот еще один пример конструкции с фигурными скобками, которая позволит массово переименовать файлы. Для получения имени файла без расширения используем basename:
for file in *.txt; do name=`basename "$file" .txt`;mv "$name{.txt,.lst}"; done
Можно это сделать проще:
$ rename .txt .lst *.txt
Что-то в третьем примере вы не используете наработки из первого.
truncate --size=1M application.log
Можно же лучше. Деаллоцировать начало лога, превратив в sparse. Как-то так:
fallocate -o 0 --punch-hole -l 32M application.log
А тут есть какой-то скрытый смысл в разбиении команды на две? (при том что вторая выполнится независимо от первой, лучше наверно ;
заменить на &&
)
STRING=$(tail -n 1000 application.log);echo "$STRING" > application.log
Или можно переписать вот так без вреда для здоровья?
echo "`tail -n 1000 application.log`" > application.log
Для статистики, мне больше всего понравилось вот это ))
Довольно изощрённо, но элегантно.
for file in *.txt; do mv "${file%.txt}{.txt,.lst}"; done
Ну и про издевательства над переменными при выводе не знал
Это никак не меняет того, что это будет две команды, просто меняется условие выполнения второй команды, но их все равно остается две.
2. echo "`tail -n 1000 application.log`" > application.log
Можно и так, но меня уже убедили, что лучше будет echo "$(tail -n 1000 application.log)" > application.log
3. спасибо =)
при && вторая команда не будет выполняться если упадет первая. Когда вторая зависит от первой, лучше применять &&. например, если application.log не существует, он будет ошибочно создан пустым в оригинальном примере
если опасаться того, что пароль при запуске программы:
можно будет увидеть в списке процессов, где отображается вся командная строка со всеми аргументами,
то такую команду тоже нельзя запускать:
$ echo "secretpassword" > secret.key; chmod 600 secret.key
тогда также и нельзя допускать вероятность чтения такого файла и права доступа должны быть установлены до записи в файл или в момент записи, поэтому:
$ touch secret.key && chmod 600 secret.key && cat >> secret.key
password<enter><eof>
или
$ (umask 0177 && cat > secret.key)
password<enter><eof>
echo это внутренняя команда (несмотря на существование /bin/echo, приоритетом будет внутренняя команда). Поэтому как отдельный процесс она не запускается и ее не будет видно в списке процессов — смело можно делать echo «secretpassword».
Если у вас есть подозрение, что на этом же сервере сидит злоумышленник и ждет момента прочитать secret.key, пока на нем пару милисекунд есть права для чтения — лучше сразу поменять сервер =)
Про echo
по всей видимости вы правы, даже для программы echo
лежащей в текущей папке необходимо указать путь ./
для её запуска. Есть всё же возможнотсь и echo
заставить работать по другому, например при помощи alias
но это может быть только по ошибке/фиче сделаной кем-то из самих админов но это не будем рассматривать. Но всё же такой подход необходимо если не использовать то знать обязательно. Так как в следующий момент это окажется не echo
а другая команда.
А по поводу:
Если у вас есть подозрение, что на этом же сервере сидит злоумышленник и ждет момента прочитать secret.key, пока на нем пару милисекунд есть права для чтения — лучше сразу поменять сервер =)
Пока что разделямый хостинг не отменили. И вот пока он существует, все команды управления на нём выполняются в среде где сидит такой злоумышленник. Так-же и на хостинге/облаке и т.п., где часть выполняемых задач являются предоставляемым сервисом. Тоесть такой VPS и т.п., где клиент-владелец может например из панели устанавливать скажем софт. То клиент-владелец этого VPS и есть этот злоумышленник. Если имеются данные (не только пароли) которые к клиенту не должны попасть то их необходимо защищать подобным образом.
Ибо владелец VPS, может просто зайти под рутом и посмотреть данные прямо у вас в файле, или даже не пароли, а сразу нужные ему данные в базе вашего проекта, а не ждать пока вы запустите процесс с небезопасным аргументом.
про злоумышленников-владельцев VPS предоставляющих shared sites
я не про владельцев разделяемого хостинга а про клиентов, когда сосед может наблюдать за действиями соседа. Аналогично и на рабочем сервере обычный пользователь, может наблюдать за некоторыми действиями админа или другого пользователя.
Сейчас получается что вы вроде как формируете такой сценарий где нет злоумышленника под задачу где есть злоумышленник. Это есть изменение условий задачи. Если в вашей задаче нет злоумышленника то и защищать не нужно — это хорошая задача. В реальности имеется вероятность что есть злоумышленник. Это могут быть дыры в сайте или человеческий фактор, и т.п. не важно как он появился и какие у него цели — он должен быть лишён малейшей вероятной возможности получить доступ к данным.
Всё же следует учитывать то что на echo
может быть установлен alias
например с целью логгирования или ещё за какой надобностью. Хотя бы даже и временный alias
. Так что скрипты и действия должны быть написаны и выполняться с учётом того что echo
может не являться встроенной командой.
Скрипты и действия должны выполняться с учетом того, что вы контролируете ваше окружение. Если же вы пишете скрипты, которые будут выполняться неизвестно в каких условиях, то echo будет далеко не единственной вашей проблемой.
Как это уже говорилось выше, лучше пользоваться ssh ключами, сертификатами и другими способами авторизации, которая вообще не предполагает передачу именно пароля прямым текстом через аргументы или пайпы.
Конечно лучше. Но исходная задача была использование echo
с приватными данными. Её нужно выполнять как в моём примере.
с учетом того, что вы контролируете ваше окружение
Проконтролировать окружение — это не значит что вы как старший админ гарантируете что по вашей инструкции никто не должен ставить alias
на echo
(вы или другой ведь могли отменить это, когда востребовано, забыв что есть чувствительный к этому код). Гарантировать окружение это значит перед выполнением команды echo
с приватными данными скрипт должен проверить а не установлен ли alias
на echo
. Тогда это будет гарантия. Возможно скорее всего такой alias
был востребован и написан правильно, но теперь echo
является программой. Проще всё же в таком случае просто сразу считать echo
программой.
Обычный пользователь не может прописать алиас другому обычному пользователю. Поэтому если вы даете программу тому, кто не умеет настраивать безопасное окружение, не стоит надеяться, что вы предусмотрите подобные моменты в скриптах — это как раз неправильный путь.
Есть ожидаемое поведение, в котором echo — внутренняя команда. На это можно рассчитывать, если ваше окружение настраивает адекватный админ.
Если писать приложение, которое требует безопасности, с рассчетом, что его будет настраивать криворукий и жутко любопытный пользователь — боюсь, что скрипты вообще лучше не трогать, так как он и туда залезет =)
Возможно, ваш скрипт в таком случае, должен также проверить, какой именно шелл его выполняет, а точно ли этот шелл взят из репозитория, а точно ли этот репозиторий не скомпроментирован и так далее.
Не "возможно", а именно всё это обязательно делается при установке и настройке ос. Какой именно шелл а также версия прочих компиляторов, инструментов и библиотек проверяется/указывается при развёртывании. И отпечатки к при доступе к репозиторию контролируются. Да всё это необходимо делать. Более того для всего кода пишутся тесты и выполняются в среде аналогичной рабочей. А вот alias
не проверяется так как является правильным рабочим инструментом с "ожидаемым поведением" так-же как и echo
.
Вот конкретно установка alias
на echo
— и написание чувствительного к этому кода — грабли. Код чувствительный к этому писать нельзя или можно надеяться что никогда не будет установлен alias — с этим вроде всё ясно.
...
Конструкция «mv "$name{.txt,.lst}"» не работает.
Во-первых, находясь в кавычках brace expansion не разворачивается и во-вторых, то, что закавычено, воспринимается одним параметром и в результате mv возвращает "missing destination file operand after (подставленная переменная){.txt,.lst}.".
«mv $name{.txt,.lst}» работает если в имени нет пробелов, а «mv "$name"{.txt,.lst}» должно работать и с пробелами.
По поводу первого замечания в мане написано: "A correctly-formed brace expansion must contain unquoted opening and closing braces, and at least one unquoted comma or a valid sequence expression. Any incorrectly formed brace expansion is left unchanged."
Насчет bash variable expansions, пример
$ VAR="Hello my friend (enemy)"
$ TEMP="${VAR##*\(}"
$ echo "${TEMP%\)}"enemy
Можно переписать так:
VAR="Hello my friend (enemy)"
echo ${VAR//[()]/}
И тут будет полезно пояснить, что конструкция вида ${val/from/to} заменяет первое вхождение подстроки "from" на подстроку "to", но если вместо первой косой черты сделать двойную // то замена будет глобальной, а внутри [] вместо подстроки (или вместе с подстрокой на равных с символом *), можно указать массив отдельных символов для замены, так как после последней косой черты ничего нет, указанные в квадратных скобках символы глобально вырезаются и ничем не заменяются.
Это очень удобно и очень сильно облегчает понимание парсига, для того кто знаком с этими приемами.
Узнал я об этом когда попросил чат-жи-пи-ти написать мне коротенький скрипт парсинга, дополнив промпт просьбой написать коротко и лаконично, "bash-like style, так чтобы код понравился гуру bash-программирования". Увидев непонятный прием, попросил пояснить его. Тут-то мне и открылось тайное знание!
Дюжина приемов в Linux, которые действительно сэкономят уйму времени