Переместите произвольную строку в конец команды

Вопрос или проблема

Я часто замечаю, что придумал какое-то заклинание, которое хочу повторно использовать, но оно слишком подвержено спонтанным изменениям, чтобы его можно было сохранить как функцию. Эти команды часто содержат какую-то критически важную переменную, такую как имя файла, глубоко запрятанную внутри. Я хотел бы перенести этот компонент в конец команды.

Например, моя последняя магия выводит определения таблиц всех новых 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'

Заключение

Таким образом, существует несколько подходов к перемещению произвольных строк в конец команд и управлению параметрами. Выбор документации будет зависеть от ваших потребностей в читабельности, удобстве редактирования и частоте выполнения команд.

Каждый из этих методов сохраняет структурированность исходной команды и предоставляет возможность дальнейшего редактирования без дополнительных усилий. Важно выбрать тот подход, который наиболее удобен и соответствует вашим индивидуальным требованиям к работе.

Оцените материал
Добавить комментарий

Капча загружается...