Вопрос или проблема
Я часто замечаю, что придумал какое-то заклинание, которое хочу повторно использовать, но оно слишком подвержено спонтанным изменениям, чтобы его можно было сохранить как функцию. Эти команды часто содержат какую-то критически важную переменную, такую как имя файла, глубоко запрятанную внутри. Я хотел бы перенести этот компонент в конец команды.
Например, моя последняя магия выводит определения таблиц всех новых INSERT
операторов в файл diff:
for T in $(grep -oP "(?<=^\+INSERT INTO \`)[\w-]+(?=\`)" foo.diff | sort -u) ; do docker-compose exec mysql mysqldump --no-data some_database $T | grep -P "^\s*\`|^CREATE|^\) |^\s*CONSTRAINT" | sed "s/ COMMENT .*,$/,/" | sed "s/CONSTRAINT \`\w*\` //" | sed "s/^) ENGINE.*;/);/" ; done
Отформатировано для удобочитаемости:
for T in $(grep -oP "(?<=^\+INSERT INTO \`)[\w-]+(?=\`)" foo.diff | sort -u)
do
docker-compose exec mysql mysqldump --no-data some_database $T
| grep -P "^\s*\`|^CREATE|^\) |^\s*CONSTRAINT"
| sed "s/ COMMENT .*,$/,/"
| sed "s/CONSTRAINT \`\w*\` //"
| sed "s/^) ENGINE.*;/);/"
done
Я очень вероятно захочу использовать это снова в будущем на файле, который не называется foo.diff
, поэтому я бы хотел, чтобы команда заканчивалась именем файла. Мое первое стремление было обернуть все это в функцию и сделать имя файла параметром $1.
Однако скорее всего я снова доработаю это во время его выполнения. Добавлю немного sed здесь, может быть, touch awk там. Поэтому я все равно хотел бы, чтобы вся команда оставалась в CLI, когда я нажимаю Ctrl-P
, чтобы запустить ее снова. Поэтому я решил использовать переменную:
$ FILENAME=diff-03-04.sql
Это подходит для этого конкретного примера, так как я буду использовать один и тот же файл для каждого заклинания сегодня, но существуют и другие примеры, где я действительно хотел бы иметь возможность просто изменить конец команды. Так что это:
$ for Q in $(foo Torvalds bar) ; do baz $Q ; done
Стало бы чем-то похожим на (но не обязательно точно):
$ for Q in $(foo SOME_MAGIC_HERE bar) ; do baz $Q ; done < Torvalds
Мне кажется, что вы не поняли силу сценария или функции. Лично я предпочитаю сценарии, потому что они самостоятельны и доступны независимо от интерактивной оболочки, которую я использую, но функция может выполнять абсолютно ту же задачу. Для меня системные сценарии размещаются в /usr/local/bin
, а личные (бесполезные) сценарии – в ~/bin
. Оба находятся в моем $PATH
.
Создайте каталог ~/bin
. Добавьте его в ваш $PATH
(export PATH="$PATH:$HOME/bin"
). Теперь любой исполняемый скрипт, размещенный в этом каталоге, будет доступен вам как новая команда.
Возьмем ваш пример как случай в точку. Вы хотите, чтобы имя файла было выбором во время использования, так что давайте сделаем его первым аргументом сценария.
Создайте файл ~/bin/tables
:
#!/bin/bash
file=$1
for T in $(grep -oP "(?<=^\+INSERT INTO \`)[\w-]+(?=\`)" "$file" | sort -u)
do
docker-compose exec mysql mysqldump --no-data some_database "$T"
| grep -P "^\s*\`|^CREATE|^\) |^\s*CONSTRAINT"
| sed "s/ COMMENT .*,$/,/"
| sed "s/CONSTRAINT \`\w*\` //"
| sed "s/^) ENGINE.*;/);/"
done
Сделайте сценарий исполняемым с помощью chmod a+x ~/bin/tables
, и теперь у вас есть новая команда.
tables foo.diff
tables another.diff
Все здесь дали отличные советы, как в другом ответе, так и в комментариях. Лучший метод, который я нашел, – это обернуть команду в функцию и выполнить эту функцию в одной команде:
$ _() { echo $1; } && _ beer
beer
$
Кроме того, я заменил синтаксис for T in $()
на cmd | while read
, как предложил Кусаланда. Таким образом, окончательный синтаксис выглядит следующим образом:
_() { grep -oP "(?<=^\+INSERT INTO \`)[\w-]+(?=\`)" $1 | sort -u | while read -r T ; do docker-compose exec mysql mysqldump --no-data some_database $T | grep -P "^\s*\`|^CREATE|^\) |^\s*CONSTRAINT" | sed "s/ COMMENT .*,$/,/" | sed "s/CONSTRAINT \`\w*\` //" | sed "s/^) ENGINE.*;/);/" ; done } && _ foo.diff
Отформатировано для удобочитаемости:
_() {
grep -oP "(?<=^\+INSERT INTO \`)[\w-]+(?=\`)" $1
| sort -u
| while read -r T ;
do
docker-compose exec mysql mysqldump --no-data some_database $T
| grep -P "^\s*\`|^CREATE|^\) |^\s*CONSTRAINT"
| sed "s/ COMMENT .*,$/,/"
| sed "s/CONSTRAINT \`\w*\` //"
| sed "s/^) ENGINE.*;/);/"
done
}
&& _ foo.diff
Этот метод сохраняет возможность вносить изменения в любом месте команды, где это необходимо, и в то же время легко менять аргумент (в данном случае имя файла), не нужно экранировать кавычки в самих командах.
Еще один синтаксис, который я использовал в последнее время:
$ xargs -I {} bash -c 'echo {}' <<< beer
Ответ или решение
Вопрос о том, как переместить произвольную строку в конец команды, представляет собой довольно интересную задачу для IT-специалиста. В этом ответе мы обсудим несколько способов достижения этой цели, нежно держа в уме возможность дальнейшего редактирования командной строки и добавления переменных.
1. Использование переменных
Первый и наиболее очевидный способ — использовать переменные для хранения изменяемых элементов команды. В вашем случае это может быть имя файла. Этот метод позволяет быстро и удобно вносить изменения в команду без необходимости переписывать её целиком.
Пример:
FILENAME=foo.diff
for T in $(grep -oP "(?<=^\+INSERT INTO \`)[\w-]+(?=\`)" "$FILENAME" | sort -u) ; do
docker-compose exec mysql mysqldump --no-data some_database "$T" |
grep -P "^\s*\`|^CREATE|^\) |^\s*CONSTRAINT" |
sed "s/ COMMENT .*,$/,/" |
sed "s/CONSTRAINT \`\w*\` //" |
sed "s/^) ENGINE.*;/);/"
done
Теперь вам нужно лишь изменять значение переменной FILENAME
, чтобы выполнять команду с другим файлом.
2. Обертывание в функцию
Если вы планируете часто использовать команду с различными файлами и дополнительно хотите сохранять читабельность кода, вы можете обернуть вашу команду в функцию. Это позволяет сохранять структуру команды, одновременно меняя аргументы.
Пример функции:
tables() {
local file="$1"
for T in $(grep -oP "(?<=^\+INSERT INTO \`)[\w-]+(?=\`)" "$file" | sort -u) ; do
docker-compose exec mysql mysqldump --no-data some_database "$T" |
grep -P "^\s*\`|^CREATE|^\) |^\s*CONSTRAINT" |
sed "s/ COMMENT .*,$/,/" |
sed "s/CONSTRAINT \`\w*\` //" |
sed "s/^) ENGINE.*;/);/"
done
}
Запускаете команду, просто указывая файл:
tables foo.diff
3. Использование xargs
Наконец, еще один способ — это использовать команду xargs
, которая прекрасно справляется с задачами, требующими передачи параметров.
Пример использования xargs
:
echo foo.diff | xargs -I {} bash -c 'for T in $(grep -oP "(?<=^\+INSERT INTO \`)[\w-]+(?=\`)" {} | sort -u); do docker-compose exec mysql mysqldump --no-data some_database "$T" | grep -P "^\s*\`|^CREATE|^\) |^\s*CONSTRAINT" | sed "s/ COMMENT .*,$/,/" | sed "s/CONSTRAINT \`\w*\` //" | sed "s/^) ENGINE.*;/);/"; done'
Заключение
Таким образом, существует несколько подходов к перемещению произвольных строк в конец команд и управлению параметрами. Выбор документации будет зависеть от ваших потребностей в читабельности, удобстве редактирования и частоте выполнения команд.
Каждый из этих методов сохраняет структурированность исходной команды и предоставляет возможность дальнейшего редактирования без дополнительных усилий. Важно выбрать тот подход, который наиболее удобен и соответствует вашим индивидуальным требованиям к работе.