Открытый )ssh-клиент (попытается прочитать определенные имена файлов , предполагая, что они содержат ваш закрытый ключ:
~/.ssh/identity
~/.ssh/id_dsa
~/.ssh/id_ecdsa
~/.ssh/id_ed25519
~/.ssh/id_rsa
Contains the private key for authentication.
Поскольку у вас есть каталогid_rsa
, вам нужно будет использовать опцию -i
, чтобы указать ssh
, где находится ваш закрытый ключ:
-i identity_file
Selects a file from which the identity (private key) for public key authentication is read.
Либо переместите свои закрытые ключи напрямую в каталог ~/.ssh/
, стараясь временно переименовать любой файл ключа id_rsa
, чтобы можно было удалить пустой каталог id_rsa
. Если вы затем назовете свой ключ (s )с именем файла, которое ожидает ssh
, вы можете избежать необходимости указывать -i
все время.
bracket.awk
:
BEGIN{quote=1}
{
for(i=1;i<=length;i++){
ch=substr($0,i,1)
pr=1
if(ch=="\""){quote=!quote}
else if(ch=="[" && quote){brk++;pr=brk<2}
else if(ch=="]" && quote){brk--;pr=brk<1}
if(pr){printf "%s",ch}
}
print ""
}
$ awk -f bracket.awk file
["q", "0", "R", "L"], ["q", "1", "[", "]"], ["q", "2", "L", "R"], ["q", "3", "R", "L"]
Идея:
Инициализировать quote=1
. Прочтите файл char -. Всякий раз, когда кавычка найдена, инвертируйте quote
переменную (, если 1
, она становится 0
, и наоборот -наоборот ).
Затем скобки подсчитываются, только если quote
установлено на 1 и лишние скобки не печатаются в соответствии со счетчиком brk
.
Оператор print ""
просто добавляет новую строку, а оператор printf
выше этого не делает.
(Этот ответ является скорее экспериментом, чтобы увидеть, как будет выглядеть «применение правильного инструмента для работы» — в данном случае clojure. Действительно, я пришел написать этот ответ именно потому, что решение в clojure пришло мне в голову примерно через 10 секунд после прочтения вопроса, в сочетании с фактом о (read)
, о котором я упомяну ниже. Остальное - настоящая «проблема» в этом ответе - было 90-минутной попыткой бороться с интерактивными корнями шепелявости. Эта борьба мне знакома; SML, особенно его реализация в Нью-Джерси, страдает тем же недостатком.)
Лисп — очевидный выбор для обработки структур данных, -таких как списки. На самом деле в clojure эта проблема решается с помощью (flatten my-list)
или (map flatten list-of-lists)
!
Но мы еще не закончили, если хотим, чтобы ввод и вывод точно соответствовали тому, что определено в вопросе. Здесь clojure борется под собственным весом :он был создан для интерактивного использования (как многие lisps)или для использования в качестве java-программы (запускает основной метод ). Ни один из них на самом деле не облегчает традицию фильтрации unix по чтению со стандартного ввода / записи на стандартный вывод. Итак, мы решим эту задачу несколькими разными способами, каждый из которых будет более или менее полезным/возмутительным.
Мы будем опираться на следующие интересные факты:
(read)
читает один объект из стандартного in; не строка, как во многих других языках, а отдельная форма clojure (, такая как выражение s -или вектор ). cat
Уже заметив, что исходный ввод является допустимым clojure, мы обходим его чтение как ввод из clojure, вводя его непосредственно в программу clojure (и оставляем sed
для довольно скучного форматирования):
#! /usr/bin/env bash
clojure -e '(->> ['"$(cat)"'] (map flatten) (map vec) (apply prn))' \
| sed -e 's/ /, /g'
Выполнение этого приводит к
$./nested-clj-cat <unix.in
["q", "0", "R", "L"], ["q", "1", "[", "]"], ["q", "2", "L", "R"], ["q", "3", "R", "L"]
Хитрость в этом решении представляет собой смесь правильного цитирования, действительно полезного cat
и тонкого -, но -необходимого непрерывного принуждения к векторам.
clojure
в качестве интерпретатора Разве этот внутренний скрипт clojure не был бы намного удобнее в сопровождении, если бы мы могли переместить его в файл, содержащий код, а не строку в кавычках? (Я говорю это совершенно серьезно, несмотря на традицию в большинстве сценариев оболочки вызывать awk
/ sed
/ python
/ perl
встроенные строки!)
Но теперь мы должны иметь дело со стандартом чтения в; к сожалению, (read)
считывает только один объект за раз, в то время как входные данные представляют собой серию объектов.Мы могли бы массировать данные, добавляя [
в начало и ]
в конец:
sed -e 's/^/[/' -e 's/$/]/'
Но тогда вызывающая сторона должна помнить об этом, иначе исходная программа должна быть скорректирована.
Итак, мы создадим функцию read-all
, которая считывает все объекты в потоке и возвращает эту последовательность. Затем мы применим нашу технику из предыдущего:
#! /usr/bin/env clojure
(require '[clojure.java.shell :as shell])
(defn read-all
[stream]
(loop [acc []]
(let [red (binding [*read-eval* false]
(read {:eof :eof} stream))]
(if (= red :eof)
acc
(recur (conj acc red))))))
(->> (read-all *in*)
(map flatten)
(map vec)
(apply prn))
У этого есть еще один недостаток, :нам по-прежнему нужно sed
в конце, чтобы получить точные данные! В противном случае:
$./nested-clj-read <unix.in
["q" "0" "R" "L"] ["q" "1" "[" "]"] ["q" "2" "L" "R"] ["q" "3" "R" "L"]
что просто не совсем правильно. Возможно, мы можем исправить это в clojure?
Оказывается, верно одно из следующего:
str
,format
)или Я подозреваю последнее только потому, что clojure позволяет очень легко передавать структуры данных -между программами в качестве доказательства структуры данных -(prn
/read
и формата EDN ). Я не возился с обычным -средством форматирования лиспов cl-format
, которое, как я знаю, способно на это, потому что я полагал, что это может быть слишком много шепеляваний в одной и той же мешанине языков :)
Если кто-то может решить это более элегантно, я был бы рад обсудить это.
В конце концов, я прибег к внедрению вызовов sed
внутрь clojure — это избавляет вызывающую сторону от необходимости помнить о ее вызове за счет еще большего усложнения кода. Чтобы все было красиво и читабельно, я ввожу макрос канала:
(defmacro |
[cmd in]
`(:out (shell/sh ~@cmd :in ~in)))
Это должен быть макрос, потому что apply
не будет работать со списками, отличными от -, после списков, и я действительно хочу, чтобы in
был последним параметром (, чтобы он подходит к->>
). Увы,из-за реализации sh
с использованием фьючерсов нам нужен вызов (shutdown-agents)
, чтобы исключить ожидание в течение нескольких минут после завершения сценария, чтобы он завершился.
Таким образом, финальный сценарий
#! /usr/bin/env clojure
(require '[clojure.java.shell :as shell])
(defn read-all
[stream]
(loop [acc []]
(let [red (binding [*read-eval* false]
(read {:eof :eof} stream))]
(if (= red :eof)
acc
(recur (conj acc red))))))
(defmacro |
[cmd in]
`(:out (shell/sh ~@cmd :in ~in)))
(->> (read-all *in*)
(map flatten)
(map vec)
(apply prn-str)
(| ["sed" "-e" "s/ /, /g"])
print)
; needed because of shell/sh's use of futures
(shutdown-agents)
И результаты:
$./nested-clj-read-with-sed <unix.in
["q", "0", "R", "L"], ["q", "1", "[", "]"], ["q", "2", "L", "R"], ["q", "3", "R", "L"]
Отлично.
Другие языки обладают огромными преимуществами, когда речь идет о правильном рекурсивном манипулировании данными. Однако они не всегда упрощают работу в качестве unix-фильтра -, и попытка втиснуть их в этот мир часто приводит к увеличению сложности. Даже короткое cat
решение должно заставить рецензента задуматься — не потому, что его трудно понять, а потому, что оно достаточно неясно и требует некоторого размышления.
Тем не менее, возможно, стоит рассмотреть другие языки при манипулировании определенными формами данных :, в то время как все другие решения sed/perl/awk, которые я просматривал здесь, не имели проблем со чтением и записью данных, им приходилось делать совсем немного работы, чтобы манипулировать ею. В некоторых случаях я бы назвал этот уровень работы непосильным из-за сложности, необходимой для понимания команд (, и я сам ежедневно использую эти инструменты )! Это не означает, что мои решения clojure менее непроницаемы, а скорее то, что у нас есть две стороны медали, которая действительно хочет быть тором :дайте мне и то, и другое, дайте мне простоту знания фильтров ввода-вывода и простота обработки данных, присущая lisps/ML.
В сторону :Интересно, сможет ли jq
решить эту проблему после того, как мы обернем ввод в {}
?
Мы можем решить эту проблему с помощью парадигмы функционального программирования, как только поймем, что входная строка является допустимым списком списков (@LoLs ).
Таким образом, мы сохраняем строку в текстовом файле и вызываем ее внутри perl-кода. Что он делает, так это бесплатно создает для нас действующую структуру данных Perl.
Теперь мы просто используем команду рекурсивной карты, чтобы разбить список на отдельные элементы. А затем оформите их в кавычки и разделите запятыми.
#! /bin/env bash
echo '@::LoLs = ([[["q", "0"], "R"], "L"], ["q", [["1", "["], "]"]], [["q", ["2", "L"]], "R"], ["q", ["3", ["R", "L"]]])' > code.pl
perl -wMstrict -le '
local $" = ", "; #list separator
use constant A => q[ARRAY];
do "./code.pl";
print join q[, ], map { qq([$_]) }
map {
$_[0] ||= sub {
"@{[map {
+ref eq A ? $_[0]->($_[0],@$_) : qq(\"$_\");
} splice @_,1]}"; #end inner map
}; #end sub
$_[0]->($_[0],$_);
} @::LoLs; # end outer map
'
Результат:
["q", "0", "R", "L"], ["q", "1", "[", "]"], ["q", "2", "L", "R"], ["q", "3", "R", "L"]
Я опубликовал этот вариант только потому, что вы сказали:
I understand how an algorithm could be written that does this by pushing and popping a stack or just incrementing and decrementing a counter
На самом деле я бы просто использовал счётчик.
$ cat tst.awk
{
$0 = encode($0)
sep = ""
while ( match($0,/\[[^][]+]/) ) {
if ( prevRstart && (RSTART > prevRstart) ) {
printf "%s%s", sep, decode(prevStr)
sep = ", "
}
prevStr = substr($0,RSTART,RLENGTH)
prevRstart = RSTART
$0 = substr($0,1,RSTART-1) "<" substr($0,RSTART+1,RLENGTH-2) ">" substr($0,RSTART+RLENGTH)
}
printf "%s%s\n", sep, decode(prevStr)
}
function encode(str) {
gsub(/@/,"@A",str)
gsub(/[{]/,"@B",str)
gsub(/}/,"@C",str)
gsub(/</,"@D",str)
gsub(/>/,"@E",str)
gsub(/"\["/,"{",str)
gsub(/"]"/,"}",str)
return str
}
function decode(str) {
gsub(/[<>]/,"",str)
gsub(/}/,"\"]\"",str)
gsub(/[{]/,"\"[\"",str)
gsub(/@E/,">",str)
gsub(/@D/,"<",str)
gsub(/@C/,"}",str)
gsub(/@B/,"{",str)
gsub(/@A/,"@",str)
return str
}
.
$ awk -f tst.awk file
["q", "0", "R", "L"], ["q", "1", "[", "]"], ["q", "2", "L", "R"], ["q", "3", "R", "L"]
См.https://stackoverflow.com/a/35708616/1745001для получения дополнительной информации о том, что эти подпрограммы ()s (в этом вопросе )делают для кодирования/декодирования этих значимых символов и строк, необходимых для того, чтобы иметь возможность изолировать [...]
струны.
Таким образом, -он находит [...]
строки изнутри наружу, другими словами, учитывая [ [ foo ] ]
match("[ [ foo ] ]",/[[^][]/)
соответствует [ foo ]
, затем мы меняем [
на <
и ]
до >
, так что в следующий раз в цикле match("[ < foo > ]",/[[^][]/)
соответствует всей строке. Затем нам просто нужно удалить <
и >
перед печатью [ foo ]
. Он узнает, что найден самый внешний [...]
, когда в следующий раз в цикле совпадающая строка начинается в позиции за пределами предыдущей начальной позиции (, т.е. не находится внутри предыдущей совпадающей строки ), и в это время она печатает все, что предыдущая совпадающая строка была.
Это gawk
неэлегантно, если не сказать больше, оно сломается, если вы даже будете смотреть на него слишком долго, так что вам не нужно мне говорить........ просто тихо и самодовольно -посмейтесь над тем, что вы можете добиться большего.
Но так как это более или менее работает (по средам и пятницам в месяцы с J
в них )и занимает 20 минут моей жизни, я все равно публикую это
Шредингераawk
(Спасибо @edmorton)
awk -F"\\\], \\\[" '
{printf "[";
for (i=1; i<=NF; i++) {
cs=split($i,c,",");
for (j=1; j<=cs; j++){
sub("^ *\\[+","",c[j]); sub("\\]+$","",c[j]);
t=(j==cs)?"]"((i<(NF-1))?", [":""):",";
printf c[j] t
}}print ""}' file
["q", "0", "R", "L"], ["q","1", "[", "]"], ["q","2", "L", "R"], ["q","3","R", "L"]
Прохождение
Разделите поля -F
на ], [
, которые нужно сбежать в ад и обратно, чтобы получить ваши окончательные группы элементов в полях.
Затем split
на ,
, чтобы получить элементы и использовать все начальные ^[
или конечные ]$
из каждого элемента,затем повторно -агрегируйте split
с ,
в качестве разделителя и, наконец, повторно -агрегируйте поля, используя условную комбинацию ]
и , [
.
Гейзенбергаsed
Если вы перейдете к sed
, это будет немного опрятнее
awk 'BEGIN{FS="\\], \\["}{for (i=1; i<=NF; i++) print $i}' file |
sed -E "s/(^| |,)\[+(\")/\1\2/g ;s/\]+(,|$)/\1/g" |
awk 'BEGIN{RS=""; FS="\n";OFS="], ["}{$1=$1; print "["$0"]"}'
["q", "0", "R", "L"], ["q", "1", "[", "]"], ["q", "2", "L", "R"], ["q", "3", "R", "L"]
Выполняет ту же работу, что и первая версия, первая awk
разделяет поля, как и раньше, sed
теряет лишнее [
и ]
, а последняя awk
перекомпоновывает элементы, переопределяя RS
, FS
иOFS
perl
:perl -pe '
s{([^]["]+|"[^"]*")|\[(?0)*\]}
{$1 // "[". ($& =~ s/("[^"]*"|[^]["]+)|./$1/gr). "]"}ge'
Это использует рекурсивное регулярное выражение perl
.
Внешний s{regex}{replacement-code}ge
токенизирует ввод в один из:
[
, ]
или"
[...]
(использует рекурсию в регулярном выражении для поиска соответствия]
)Затем мы заменяем этот токен самим собой, если он находится в первых двух категориях ($1
), а если нет, токен с не -заключенным в кавычки [
, ]
, удаленным с использованием того же метода токенизации во внутреннем замена.
Чтобы обрабатывать экранированные кавычки и обратную косую черту внутри кавычек (, например "foo\"bar\\"
), замените [^"]
на (?:[^\\"]|\\.)
.
sed
Если ваш sed
поддерживает опции -E
или -r
для работы с расширенными регулярными выражениями вместо базовых , вы можете сделать это с помощью цикла,замена самого внутреннего [...]
на первый:
LC_ALL=C sed -E '
:1
s/^(("[^"]*"|[^"])*\[("[^"]*"|[^]"])*)\[(("[^"]*"|[^]["])*)\]/\1\4/
t1'
(использование LC_ALL=C
для ускорения и эквивалентности perl
, который также игнорирует языковой стандарт пользователя, когда дело доходит до интерпретации байтов как символов ).
С точки зрения POSIX, это все еще должно быть выполнимо с чем-то вроде:
LC_ALL=C sed '
:1
s/^\(\(\("[^"]*"\)*[^"]*\)*\[\(\("[^"]*"\)*[^]"]*\)*\)\[\(\(\("[^"]*"\)*[^]["]*\)*\)\]/\1\6/
t1'
Здесь используется \(\(a\)*\(b\)*\)*
вместо (a|b)*
, так как базовые регулярные выражения не имеют оператора чередования (BRE некоторых sed
реализаций имеют \|
для этого, но это не POSIX/portable ).
Используя рекурсивное регулярное выражение в Perl, мы можем выполнить необходимое выравнивание переменной.
Рекурсивно захватить 3 типа фрагментов, часть в кавычках, часть без кавычек, но не запуская другую вложенность, и третью вложенную скобку, которая не заключена в кавычки.
После захвата фрагмента удалите знаки квадратных скобок с нечетных частей фрагмента. Нечетная часть — это часть слева от двойной кавычки.
perl -lpe '
my $re;
$re = qr{\[(?:
(?>"[^"]*") |
(?>[^]"[]+) |
(??{ $re })
)*]}x;
s/$re/
local $_ = $&;
"[".
s{([^"]*")([^"]*")|([^"]+$)}{
$1 =~ tr:[]::dr
. $2.
$3 =~ tr:[]::dr
}xger
. "]"
/xge;
'
выход:
["q", "0", "[", "R", "L"], ["q", "1", "[", "]"], ["q", "2", "L", "R"], ["q", "3", "R", "L"]
Ниже приведен еще один способ сгладить скобки, на этот раз с использованием нерекурсивного регулярного выражения.
echo "....." |\
perl -lne '
my $re = qr{\[ (?{ local $a = 1 })
(?:(?(?{ ! $a })(?!))
(?:
\[(?{ local $a=$a+1 }) |
\](?{ local $a=$a-1 }) |
(?>"[^"]*") |
(?>[^]"[]+)
)
)+
(?(?{ $a })(?!))
}x;
print s/($re)/"[".
$1 =~ s{([^"]*(?:"|$))}{
$|-- ? $1 : $1 =~ tr:][::dr;
}regx
. "]"/regx;
'
Мне было интересно, можно ли написать рекурсивные лямбда-выражения на Python, так что вот, пожалуйста:
echo '[[["q", "0"], "R"], "L"], ["q", [["1", "["], "]"]], [["q", ["2", "L"]], "R"], ["q", ["3", ["R", "L"]]]' | python -c 'import sys, ast; print([(lambda f: f(f))(lambda f, i=top_level_list: [e for a in i for e in (f(f,a) if isinstance(a, (tuple, list)) else (a,))]) for top_level_list in ast.literal_eval(sys.stdin.read())])'
[['q', '0', 'R', 'L'], ['q', '1', '[', ']'], ['q', '2', 'L', 'R'], ['q', '3', 'R', 'L']]
[Замените echo
на ваш стандартный вывод.] Un -один -подстроен, Python выглядит как:
my_list = [[["q", "0"], "R"], "L"], ["q", [["1", "["], "]"]], [["q", ["2", "L"]], "R"], ["q", ["3", ["R", "L"]]]
[(lambda f: f(f))(lambda f, i=top_level_list: [e for a in i
for e in (f(f,a)
if isinstance(a, (tuple, list)) else (a,))
])
for top_level_list in my_list]
Код выравнивания списка является рекурсивным, если элемент является кортежем/списком, в противном случае он создает значение. рекурсивный лямбда-код позволяет лямбде вызывать себя без имени. Остальная часть кода просто обрабатывает чтение mylist
со стандартного ввода.
Предупреждение :, если вы когда-нибудь запустите это в производство, ожидайте разговора -.
Это можно сделать с помощью sed:
sed -E ':a;s/(\[[^][]*)\[([^][]*)\]([^][]*\])/\1\2\3/;ta'
Идея состоит в том, чтобы сопоставить пару [ ]
внутри нее, сопоставить пару, чтобы удалить [ ]
, которая, в свою очередь, не содержит [
или ]
. Чтобы избежать совпадения одного [
или одного ]
, нам нужно использовать [^][]*
. Что повторяется в нескольких местах:
(\[[^][]*)
Сопоставьте (и захватите )один [
, за которым следуют несколько не [
или ]
. \[
и один[
([^][]*)
с последующим совпадением и захватом нескольких не [
или ]
. \]
и один]
([^][]*\])
, за которыми следуют несколько не [
или ]
, которые заканчиваются на ]
. Затем замените весь захват на \1\2\3
, который удалит внутреннюю пару []
.
Окружите все вышеперечисленное меткой :a
и циклом, если было сделано изменение ta
и замена повторяется до тех пор, пока больше не будут найдены внутренние []
пары (и заменены ).