Как насчет
tr -d [:blank:] < hosts | # remove trailing whitespace
perl -lne 'print join ".", reverse(split /\./)' | # reverse order of fields
sort | # sort
awk -F. '
!seen[$1.$2.$3]++ && NR>1 {print ""} # insert blank line when tld,dom,sub change
{for (i=NF;i>1;i--) printf "%s.", $i; print $1} # print fields in original order
'
предоставления
bar.foo.com
foofoo.bar.foo.com
morefoo.bar.foo.com
fufu.isub1.foo.com
sub1.foo.com
fufu.sub1.foo.com
morefu.sub1.foo.com
www.foo.com
Лучшей (я надеюсь) реализации того же алгоритма с использованием хешей в perl:
#!/usr/bin/perl
use strict;
use warnings;
my %domains = ();
while (defined($_ = )) {
chomp $_ ;
$_ =~ s/\s+//;
my @F = reverse(split(/\./));
my $domain = join(".", @F[0..2]);
if ( ! exists($domains{$domain}) ) {
$domains{$domain} = {};
}
$domains{$domain}{join(".", @F)}++;
}
foreach my $domain (sort keys %domains) {
foreach my $host (sort keys %{ $domains{$domain} }) {
print join(".", reverse(split(/\./, $host))), "\n";
}
print "\n"
}
Решение, которое вы показали выше, является общим и охватывает множество случаев. Если вы не знаете, что у вас есть проблема, вы можете принять позу YAGNI (You Ain't Gonna Need It) и просто перенаправить выбранные строки на /dev/tty.
Если вы хотите избежать этого, то вам нужно понять, как происходит жонглирование файловыми дескрипторами (fd).
Функция 3>&1
копирует stdout вызывающего контекста в "ячейку", чтобы функция могла вывести данные на любой stdout вызывающего контекста. В данном случае это /dev/tty. Помните, что функция будет вызвана с stdout, принадлежащим вложенной оболочке для подстановки ${}.
Команда exec 4>&1
создает копию fd1, которая является stdout функции. fd4 используется как ячейка для stdout, с которого начинается функция. >&3
устанавливает stdout функции/подшивки на stdout вызывающей стороны. Таким образом, весь вывод будет идти в stdoout вызывающей стороны. До конца, где >&4-
перемещает сохраненный stdout назад к началу, позволяя последнему echo "$s"
быть в stdout функции (и под-оболочки).
Ух ты!
Функции >&3
и >&4-
также можно записать более четко как 1>&3
и 1>&4-
.
В разделе руководства bash
, посвященном РЕДИРЕКЦИИ, объясняется вся эта номенклатура. Я был рад и удивлен, увидев, что на самом деле можно использовать формы {name} для замены чисел на слова.
"Каждое перенаправление, которому может предшествовать номер дескриптора файла, может вместо этого предшествовать слово формы {varname}. В этом случае для каждого оператора перенаправления, кроме >&- и <&-, оболочка выделит файловый дескриптор больше 10 и присвоит его varname. Если >&- или <&- предшествует {varname}, значение varname определяет дескриптор файла для закрытия."
Я не проверял это, но это предполагает, что вы можете закодировать это так:
#!/bin/bash
exec {caller_stdio}>&1
returnString() {
exec {func_stdio}>&1 1>&{caller_stdio}
local s=$1
s=${s:="some default string"}
echo "writing to stdout"
echo "writing to stderr" >&2
exec 1>&{func_stdio}-
echo "$s"
}
my_string=$(returnString "$*")
echo "my_string: [$my_string]"
exec 3>&1
fd 3 теперь является dup fd 1 (в вашем примере, ваш терминал). Как сказано на странице руководства, это означает, что fd 1 и fd 3 могут использоваться как взаимозаменяемые для обозначения одного и того же файла или устройства.
Основное использование дублирования в shell - это сохранение копии fd, чтобы ее можно было восстановить позже.
my_string=$(returnString "$*")
Для того чтобы bash получил вывод stdout после оценки returnString "$*"
, он создает pipe и развилку, а дочерняя программа вызывает dup2, чтобы переместить записывающий конец трубы (в этом примере это fd 5, но это может варьироваться) на fd 1.
exec 4>&1 >&3
Перенаправления оцениваются слева направо. fd 4 устанавливается как дубликат конца трубы. Затем, fd 1 устанавливается как дубликат fd, который ссылается на ваш терминал.
echo "writing to stdout"
echoes на fd 1, ваш терминал.
exec >&4-
Это перемещает (то есть, делает dup2) fd 4 на fd 1. fd 1 теперь является концом трубы.
echo "$s"
эхо на конец трубы. В конце концов, это считывается родительской оболочкой с другого конца трубы и используется как результат $(returnString "$*")