Форсирую алфавитный порядок в цикле с условиями if

Это вопрос, который я собирался разместить здесь несколько недель назад. Как и terdon , я понял, что .bashrc используется только для интерактивных оболочек Bash, поэтому нет необходимости в .bashrc , чтобы проверить, работает ли он в интерактивная оболочка. Как ни странно, все дистрибутивы, которые я использую (Ubuntu, RHEL и Cygwin), имели какой-либо тип проверки (тестирование $ - или $ PS1 ) для обеспечения текущей оболочки. интерактивен. Мне не нравится программирование культа карго , поэтому я приступил к пониманию цели этого кода в моем .bashrc .

Bash имеет особый случай для удаленных оболочек

После исследования проблемы я обнаружил, что удаленные оболочки обрабатываются по-другому. Хотя неинтерактивные оболочки Bash обычно не запускают команды ~ / .bashrc при запуске, особый случай возникает, когда оболочка вызывается удаленным демоном оболочки :

Bash пытается определить, когда он запускается со своим стандартным вводом , подключенным к сетевому соединению, как при выполнении удаленным демоном оболочки , обычно rshd , или демон защищенной оболочки sshd .Если Bash определяет, что он выполняется таким образом, он читает и выполняет команды из ~ / .bashrc, если этот файл существует и доступен для чтения. Этого не произойдет, если вызывается как sh . Параметр - norc может использоваться для запрета этого поведения, и параметр - rcfile могут использоваться для принудительного чтения другого файла, но {{ 1}} ни rshd , ни sshd обычно не вызывают оболочку с этими параметрами и не позволяют их указывать.

Пример

Вставьте следующее в начало удаленного .bashrc . (Если .bashrc получен из .profile или .bash_profile , временно отключите это во время тестирования):

echo bashrc
fun()
{
    echo functions work
}

Выполните следующие команды локально:

$ ssh remote_host 'echo $- $0'
bashrc
hBc bash
  • Нет i в $ - указывает, что оболочка неинтерактивная .
  • Отсутствие ведущих - в $ 0 означает, что оболочка не является оболочкой входа в систему .

Функции оболочки, определенные в удаленном .bashrc , также могут быть запущены:

$ ssh remote_host fun
bashrc
functions work

Я заметил, что ~ / .bashrc используется только , когда команда указывается в качестве аргумента для ssh . Это имеет смысл: когда ssh используется для запуска обычной оболочки входа, запускаются .profile или .bash_profile .bashrc предоставляется только в том случае, если это явно сделано одним из этих файлов).

Основное преимущество, которое я вижу в наличии .bashrc , полученный при запуске (неинтерактивной) удаленной команды, позволяет запускать функции оболочки. Однако большинство команд в типичном .bashrc актуальны только в интерактивной оболочке, например, псевдонимы не раскрываются, если оболочка не является интерактивной.

Удаленная передача файлов может завершиться ошибкой

Обычно это не проблема, когда rsh или ssh используются для запуска интерактивной оболочки входа или когда используются неинтерактивные оболочки для выполнения команд. Однако это может быть проблемой для таких программ, как rcp , scp и sftp , которые используют удаленные оболочки для передачи данных.

Оказывается, оболочка удаленного пользователя по умолчанию (например, Bash) неявно запускается при использовании команды scp . На странице руководства об этом не упоминается - только упоминается, что scp использует ssh для передачи данных. Это приводит к тому, что , если .bashrc содержит какие-либо команды, которые выводят на стандартный вывод, передача файлов завершится ошибкой , например, scp завершится неудачно без ошибок .

См. Также этот связанный отчет об ошибке Red Hat 15 лет назад, scp прерывается, когда есть команда echo в / etc / bashrc (который в конечном итоге был закрыт как WONTFIX ).

Почему scp и sftp не работают

SCP (безопасная копия) и SFTP (протокол безопасной передачи файлов) имеют свои собственные протоколы для локальный и удаленный концы обмениваются информацией о передаваемых файлах. Любой неожиданный текст с удаленного конца (ошибочно) интерпретируется как часть протокола, и передача не выполняется. Согласно FAQ из Snail Book

Что часто случается, однако,заключается в том, что есть операторы либо в системных файлах , либо в файлах запуска пользовательской оболочки на сервере ( .bashrc , .profile , ] /etc/csh.cshrc , .login и т. д.), которые выводят текстовые сообщения при входе в систему, предназначенные для чтения людьми (например, fortune , эхо «Привет!» и т. Д.).

Такой код должен производить вывод только при интерактивном входе в систему, когда к стандартному вводу прикреплен tty . Если он не выполнит этот тест, он вставит эти текстовые сообщения туда, где они не принадлежат: в этом случае, загрязняя поток протокола между scp2 / ] sftp и sftp-сервер .

Причина, по которой файлы запуска оболочки вообще актуальны, заключается в том, что sshd использует оболочку пользователя при запуске любых программ от имени пользователя (используя например, / bin / sh -c "команда"). Это традиция Unix и имеет преимущества:

  • Обычные настройки пользователя (псевдонимы команд, переменные среды, umask, и т. Д.) Действуют при запуске удаленных команд.
  • Обычная практика настройки оболочки учетной записи на / bin / false, чтобы отключить , это не позволит владельцу запускать какие-либо команды, если аутентификация по какой-то причине все же будет случайно успешной.

Детали протокола SCP

Для тех, кто интересуется деталями работы SCP, я нашел интересную информацию в Как работает протокол SCP , которая включает детали о Запуск scp с профилями говорящей оболочки на удаленная сторона? :

Например, это может произойти, если вы добавите это в свой профиль оболочки в удаленной системе :

echo ""

Почему он просто зависает? Это происходит из-за того, как scp в режиме source ожидает подтверждения первого сообщения протокола. Если это не двоичный 0, он ожидает, что это уведомление об удаленной проблеме, и ждет еще символов, чтобы сформировать сообщение об ошибке, пока не появится новая строка. Поскольку вы не печатали еще одну новую строку после первой, ваш локальный scp просто остается в цикле и блокируется на read (2) . Тем временем, после того, как профиль оболочки был обработан на удаленной стороне, scp в режиме приемника был запущен, который также блокирует read (2) , ожидая двоичного нуля, обозначающего начало передачи данных.

Заключение / TL; DR

Большинство операторов в типичном .bashrc полезны только для интерактивной оболочки - не при выполнении удаленных команд с rsh или ssh . В большинстве таких ситуаций установка переменных оболочки, псевдонимов и определяющих функций нежелательна, а вывод любого текста в стандартный формат является опасным при передаче файлов с помощью таких программ, как scp или sftp .Выход после проверки того, что текущая оболочка не интерактивна, является наиболее безопасным поведением для .bashrc .

2
29.11.2018, 19:59
4 ответа

Потратьте пару шагов на то, чтобы пропустить лишнее. Это ускорит процесс в целом.

declare -a lst=( Cr Hf Mo Nb Ta Ti V W Zr ) # make an array
for a in ${lst[@]}                          # for each element
do  for b in ${lst[@]:1}                    # for each but the 1st
    do [[ "$b" > "$a" ]] || continue        # keep them alphabetical and skip wasted work
        for c in ${lst[@]:2}                # for each but the first 2
        do  [[ "$c" > "$b" ]] || continue   # keep them alphabetical and skip wasted work
            for d in ${lst[@]:3}            # for each but the first 3
            do [[ "$d" > "$c" ]] || continue # keep them alphabetical and skip wasted work
                mkdir "$a$b$c$d" && echo "Made: $a$b$c$d" || echo "Fail: $a$b$c$d"
            done
        done
    done
done

Пропуски избыточности предназначены для того, когда начинаются более поздние циклы, например, когда внешний цикл находится на элементе 4, а второй цикл все еще находится на 3 или 4. Они пропускают их, потому что они не будут алфавитными комбинациями. Это также гарантирует отсутствие повторов. Это сгенерировало 126 различных каталогов без ошибок в 0m8.126s в git bash на моем ноутбуке без дополнительных оболочек, кроме mkdir.

1
27.01.2020, 21:49

Использование Perl и модуля Algorithm::Combinatorics:

perl -MAlgorithm::Combinatorics=combinations -e '$"=""; map { mkdir "@{$_}N" } combinations([qw(Cr Hf Mo Nb Ta Ti V W Zr)], 4)'

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

Как правильный Perl-скрипт:

#!/usr/bin/perl

use strict;
use warnings;

use English;
use Algorithm::Combinatorics qw(combinations);

# When interpolating a list in a string (@{$ARG} below), don't use a delimiter
local $LIST_SEPARATOR = "";

# Get all combinations, and create a directory for each combination
map { mkdir "@{$ARG}N" } combinations( [qw(Cr Hf Mo Nb Ta Ti V W Zr)], 4 );

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

Вероятно, вы могли бы сделать что-то похожее на Python...


Рекурсивная реализация оболочки (Просто для развлечения, рекурсивные функции оболочки редко бывают очень эффективными):

#!/bin/sh

build_combinations () {
    set_size=$1
    shift

    if [ "$set_size" -eq 0 ]; then
        printf 'N'
    else
        for token do
            shift
            for reminder in $(build_combinations "$(( set_size - 1 ))" "$@")
            do
                printf '%s%s\n' "$token" "$reminder"
            done
        done
    fi
}

build_combinations 4 Cr Hf Mo Nb Ta Ti V W Zr | xargs mkdir

Идея, полученная после прочтения ответа studog и вдохновения из различных фрагментов ответа на вопрос StackOverflow .

Обратите внимание, что преимуществом этого решения является то, что имена каталогов всегда заканчиваются на N. Ветвь рекурсивной остановки выводит N, а не пустую строку, благодаря чему все работает. Без его (печати пустой строки или новой строки )циклу с подстановкой команд нечего было бы зацикливать и не было бы вывода (из-за значения по умолчанию переменной IFS)].

6
27.01.2020, 21:49

Улучшение ответа @n.st, в котором используется тот факт, что элементы изначально отсортированы. Это также немного яснее, на мой взгляд.

#!/bin/bash

elements=(Cr Hf Mo Nb Ta Ti V W Zr)
len=${#elements[@]}

(( a_end = len - 3 ))
(( b_end = len - 2 ))
(( c_end = len - 1 ))
(( d_end = len - 0 ))

(( a = 0 ))
while (( a < a_end )); do
   (( b = a + 1 ))
   while (( b < b_end )); do
      (( c = b + 1 ))
      while (( c < c_end )); do
         (( d = c + 1 ))
         while (( d < d_end )); do
            mkdir "${elements[$a]}${elements[$b]}${elements[$c]}${elements[$d]}"
            (( d++ ))
         done
         (( c++ ))
      done
      (( b++ ))
   done
   (( a++ ))
done

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

Время работы:

user@host:~/so$ time./do.sh 

real    0m0.140s
user    0m0.085s
sys 0m0.044s

с

user@host:~/so$ ls -1d Cr* Hf* Mo* Nb* Ta* Ti* V* W* Zr* | wc -l
ls: cannot access 'V*': No such file or directory
ls: cannot access 'W*': No such file or directory
ls: cannot access 'Zr*': No such file or directory
126
2
27.01.2020, 21:49
#/bin/sh

# shellcheck disable=SC2046
# ^ word-splitting by the shell is intentional in this file

elems="Cr Hf Mo Nb Ta Ti V W Zr"
for a in $elems
do
    for b in $elems
    do
        for c in $elems
        do
            for d in $elems
            do
                # for a set of any four elements:
                #   string them together, separated by NUL-bytes
                #   sort them lexicographically...
                #    ... with NUL separating the elements (-z)
                #    ... and eliminate duplicates (-u)
                #   then replace the NUL bytes with line breaks
                #   allow the shell to split on those line breaks
                #   and chuck the resulting chunks into $1, $2, etc
                set -- $(printf '%s\0' "$a" "$b" "$c" "$d" | sort -z -u | tr "\0" "\n")

                # only if the current selection of elements consisted of four
                # different ones (remember we eliminated duplicates):
                if [ $# -eq 4 ]
                then
                    # create a directory, don't error out if it already exists (-p)
                    mkdir -p "$(printf '%s' "$@")"
                fi
            done
        done
    done
done

Не очень эффективные(sortвызовы даже для очевидных не -кандидатов и множественные mkdirвызовы для одного и того же имени каталога ), но максимум 9 4 = 6561 итераций внутренний цикл и с тем, что это единственный -сценарий использования, я не думаю, что стоит тратить много времени на оптимизацию.


Редактировать:
Тест на Xeon E3 -1231v3 безmkdir:

./elemdirs.sh > /dev/null  11.66s user 1.73s system 173% cpu 7.725 total

и с ним:

./elemdirs.sh > /dev/null  13.80s user 2.16s system 156% cpu 10.215 total

Выдает 126 каталогов, ожидаемое количество комбинаций с k = 4, n = 9.

6
27.01.2020, 21:49

Теги

Похожие вопросы