Используйтеawk
:
awk -v na="./." '
BEGIN{OFS=FS}
NR==FNR && NR>1 {
for(i=1;i<=NF;i++){if($i!=na){s[i]=1}}
}
NR!=FNR {
for(l in s){true}
for(i in s){if (i!=l){printf "%s"OFS,$i} else {printf "%s\n",$i}}
}
' file file
Возможно, вам придется изменить BEGIN{OFS=FS}
на BEGIN{OFS=FS="\t"}
, если ваш файл разделен табуляцией.
Пояснение:
awk... file file
)NR==FNR
и NR>1
для исключения заголовка )вы проверяете, что в ваших столбцах(i
)есть хотя бы одно значение, отличное от na="./."
, сохраните номер столбца в переменной s
. NR!=FNR
)для каждого столбца, сохраненного в s
, распечатать значение столбца. (Первый цикл позволяет узнать последний столбец, который будет напечатан (, сохраненный в переменной l
, поэтому вы можете выбрать между печатью OFS
или \n
.)Выход:
Sample1 Sample2 Sample3 Sample5
./../../../.
./../../. A/G
./../../../.
A/A A/A A/G./.
Если ваш файл был разделен табуляцией -, вывод будет немного лучше, если нет, вы можете сделать его лучше, добавив |column -t
. Тогда это будет выглядеть так:
Sample1 Sample2 Sample3 Sample5
./. ./. ./. ./.
./. ./. ./. A/G
./. ./. ./. ./.
A/A A/A A/G ./.
Потребовалось так много времени, чтобы реализовать этот подход, но описанный ниже подход сработал для меня, как и ожидалось, и я, наконец, закончил !!!
Проверено на приведенной ниже строке
Строка 1:aaabefhhhhhthkkd -> Вывод -> beftd
Строка 2:AAAbefhhhhhThkkD -> Вывод -> befTD
Строка 3:AAAbefhMThkkD -> Вывод -> befMTD
#!/bin/bash
# String passed as an input
str='AAAbefhMThkkD'
# Taking character count of provided string
count=$(echo "$str" | tr -cd 'a-z|A-Z' | wc -c)
# Dynamic array created
dynamic_array=()
# Looping through the for loop
for (( i=1 ; i<=$((count)) ; i++ ))
{
c=$(echo "$str" | cut -c "$i")
character_count=$(echo "$str" | tr -cd "$c" | wc -c)
echo "Character : $c : $character_count"
if [ "$character_count" -eq 1 ]
then
dynamic_array+=("$c")
fi
}
str_array_value="${dynamic_array[*]}" ; echo "Output : ${str_array_value// /}"
# Input : aaabefhhhhhthkkd : Output : beftd
# Input : AAAbefhhhhhThkkD : Output : befTD
# Input : AAAbefhMThkkD : Output : befMTD
Код исправлен наhttps://www.shellcheck.net
awk '
{
n=split($0, a, "")
for(i=1; i<=n; i++){
if(gsub(a[i], "") == 1){ printf("%s", a[i]) }
}
print ""
}'
n=split($0, a, "")
:a[1]
становится 1-м символом строки, a[2]
— 2-м и т. д. n
— общее количество символов. for(i=1; i<=n; i++)
:Пройдемся по всему массиву a
. if(gsub(a[i], "") == 1)
:Удалить все символы a[i]
из строки. Если в строке был удален только один символ, printf("%s", a[i])
напечатать этот символ. print ""
печатает символ новой строки после обработки всей строки. Это необязательно, если у вас есть одна строка ввода. Пример с одним уплотненным -вкладышем:
$ awk '{n=split($0,a,"");for(i=1;i<=n;i++)if(gsub(a[i],"")==1)printf("%s",a[i])}' <<< AAAbefhMThkkD
befMTD
Примечание. :Разделение нулевой строки не определено в POSIX. Однакоgawk
(GNU Awk ), mawk
иoriginal-awk
все реализуют операцию по желанию.
uniq
работает только с соседними дубликатами -, поэтому, если вы хотите использовать это, вам нужно сначала отсортировать ввод, например:
fold -w1 | sort | uniq -u | paste -sd ''
fold -w1
делает то же самое, что и ваш sed 's/./&\n/g'
, но без дополнительной ложной новой строки sort
чтобы сделать повторяющиеся символы смежными uniq -u
-u
важно печатать только синглтоны paste -sd ''
объединяет результат обратно в одну строку Из-за сортировки вы не сможете получить желаемый порядок вывода во всех случаях, например.
$ echo 'AAAbefhMThkkD' | fold -w1 | sort | uniq -u | paste -sd ''
DMTbef
Если вы не хотите внедрять собственное решение, вы всегда можете использовать PerlMoreUtils
:
$ echo 'AAAbefhMThkkD' |
perl -MList::MoreUtils=singleton -ne 'print singleton split //'
befMTD
С помощью sed
вы можете сделать что-то вроде:
sed '
:1
/\(.*\(.\).*\)\2/ { # while there is a duplicated char
s//\2\1/; # move it to the front
:2
# remove characters that are the same as the first in a loop:
s/^\(\(.\).*\)\2/\1/
t2
s/^.//
b1
}'
С реализацией GNU sed
вы можете сократить его до:
sed -E ':1;s/(.*(.).*)\2/\2\1/;T;:2;s/^((.).*)\2/\1/;t2;s/^.//;t1'
Если вы хотите выполнить проверку на наличие дубликатов без учета регистра (для того, чтобы áÁbBcδΔ
стало c
, например ), вы можете добавить флаг i
к первым 2 s
командам в Код GNU sed
выше. Однако обратите внимание, что это не будет работать для таких вещей, как German ß
vs SS
.
И это по-прежнему не будет обрабатывать эквивалентность Unicode и работать на уровне символа (, а не кластера графемы ), поэтому, например, если у вас есть aéá
, где эти буквы с акцентом выражаются в их в разложенной форме, не только U+00E9 é
не будет считаться таким же, как U+0065 U+0301 é
, но aéá
, выраженное как U+0061 U+0065 U+0301 U+0061 U+0301
, станетe
(U+0065 ), единственный не -повторяющийся символ там, даже если эти 5 символов фактически образуют 3 различных кластера графем. Мое имя в его разложенной форме стало быSt́phan
(с комбинированным острым ударением, приземляющимся на t
, когда оба e
удалены ).
Использование:
perl -Mopen=locale -lpe 's/\b{g}\Q$1\E\b{g}//gi while m/(\X)\X*\1/i'
здесь расширение ответа @sitaram(с использованием -Mopen=locale
для обработки ввода как символов вместо байтов, \X
вместо .
для сопоставления кластера графемы вместо символа и \b{g}
для графемы граница кластера )решит некоторые из этих проблем (не разбивая кластеры графем в середине, ß
против SS
), но не эквивалентность юникода:
$ echo $'groß KUSS. Ste\u0301phane, \ue9' | perl -Mopen=locale -lpe 's/\b{g}\Q$1\E\b{g}//gi while m/(\X)\X*\1/i'
groKU.Stéphane,é
(ß
замечен как дубликат SS
, e
в e\u0301
не связан с автономным e
, но два варианта é
не распознаны как одно и то же ).
Также обратите внимание, что ß/SS
будет преобразовано в /
, поскольку ß
обрабатывается первым, а SS/ß
будет преобразовано в /ß
, поскольку S
обрабатывается первым.
Это также превратит ßA/SAS
в /
, поскольку удаление дубликатов A
откроет SS
, версию ß
в верхнем регистре. Чтобы избежать этого, вы можете изменить его на:
perl -Mopen=locale -lpe 's/\b{g}\Q$1\E\b{g}/\n/gi while m/((?!\n)\X)\X*\1/i; s/\n//g'
То есть, вместо того, чтобы удалять повторяющиеся кластеры графем, мы меняем их на новую строку , предотвращая объединение символов с обеих сторон в последовательность кластеров графем, которые могут быть прописными или строчными вариантами другого кластера графем..
С GNU awk
, присваивающим FS пустую строку.
FS == ""
Each individual character in the record becomes a separate field. (This is a common extension; it is not specified by the POSIX standard.)
echo 'aaabefhhhhhthkkd' | awk -v FS= -v ORS='' '
{for (i=1; i<=NF;i++) if ( gsub($i,"&") == 1 ) print $i;print "\n"}'
beftd
Вариация на тему
echo 'aaabefhhhhhthkkd' |
awk '{while (length()>0) {t=substr($0,1,1); printf (gsub( t,"")==1)?t:""} print}'
beftd
Используйте $0
, заменив первый символ на ""
до тех пор, пока он не станет пустым, и напечатайте, когда произойдет только одна замена.
Без awk:
string="aaabefhhhhhthkkd" && discard=$(echo $string|fold -w1|sort|uniq -d|tr -d '\n') && echo $string|sed "s/[$discard]//g"
Пояснение:
string="aaabefhhhhhthkkd"
устанавливает вашу строку как переменную 'string'
&&
разделяет две команды, обеспечивая выполнение второй части после назначения переменной.
discard=$(echo $string|fold -w1|sort|uniq -d|tr -d '\n')
найдет все неуникальные символы в $string и сохранит их в переменной $discard. (развернуть на символы, отсортировать, определить не -уникальные символы, откатиться назад, сохранить в переменную)
echo $string|sed "s/[$discard]//g"
удалит символы, не являющиеся -уникальными, из строки
.Вот очень простой Perl one -лайнер (с небольшой помощьюecho
):
echo aaabefhhhhhthkkd | perl -ple 'my %z; for my $c (split q//){$z{$c}++} for my $k (keys %z){$_ =~ s/\Q$k\E//g if $z{$k} > 1}'
Пояснение:
-p
:зациклить заданную программу, каждая строка ввода находится в переменной по умолчанию $_
и распечатать каждую строку ввода после обработки -l
:обрабатывать новые строки, чтобы вам не приходилось -e
:выполните следующее как perl-код my %z
:инициализируйте хеш, чтобы сохранить количество символов в (для каждой строки)for my $c (split q//)
:разделить переменную по умолчанию, $_
, на $c
, перебирая каждый символ $z{$c}++
:увеличить количество символов$c
for my $k (keys %z)
:для каждой клавиши в %z
, количество просмотренных символов $_ =~ s/$k//g if $z{$k} > 1
:удалить все экземпляры символа $k
, если его количество больше 1 Кажется, это работает нормально:
$ echo 'aaabefhhhhhthkkd' | perl -pe 's/$1//g while m/(.).*\1/'
beftd
Довольно легко понять, даже если вы не знаете perl; это просто регулярное выражение, как оно используется в sed и любых других подобных инструментах. Даже то, как работает while, такое же, как, скажем, bash/sh, так что это тоже должно быть достаточно ясно.
Должен признаться, я не совсем понял все предложенные решения --, мне показалось, что кода слишком много для такой простой задачи. Наверное, я что-то пропустил:-(
Кроме того, если вы хотите, чтобы сравнение было нечувствительным к регистру -, добавьте флаг i
к m//
иs///g
:
$ echo 'aaabefhhHhhthkkd' | perl -pe 's/$1//gi while m/(.).*\1/i'
beftd