Classificando STDIN pelo comprimento e número de nonblanks em um Bash Script

4

Estou trabalhando no aprendizado de scripts Bash, mas estou lutando com esse problema. Dado um monte de linhas de STDIN , ordene-as primeiro pelo comprimento da linha em ordem crescente. Então, se houver linhas com o mesmo número de caracteres, classifique-as pelo número de caracteres não vazios contidos nas linhas (também em ordem crescente).

Eu tentei isso de maneiras diferentes, mas geralmente sou pego em algumas das idiossincrasias do Bash.

Aqui está o que eu tenho até agora:

#!/bin/bash

sorted=()
while IFS='' read -r line; do
    length=${#line}
    if [[ ${sorted[$length]} == "" ]] ; then
        sorted[$length]="$line"
    else
        #non unique length
        #sorted[$length]="${sorted[$length]}\n$line"
        IFS=$'\n' arr=("${sorted[$length]}")
        arr+=("$line")

        spaces=()

        for ((i=0 ; i < ${#arr[@]} ; ++i )) ; do
            spaces[$i]=$(echo "${arr[$i]}" | sed "s: : \n:g" | grep -c " ")
        done

        arr_sorted=()

        for ((i =0 ; i < ${#spaces[@]} ; i++ )) ; do
                for ((j=0 ; j < ${#arr[@]} ; i++ )) ; do

                        this_line_length=$(echo "${arr[$j]}" | sed "s: : \n:g" | grep -c " ")
                        if [[ "$this_line_length" == "${spaces[$i]}" ]] ; then
                            arr_sorted+=("${arr[$j]}")
                            unset arr[$j]
                        fi
                done
        done


    sorted[$length]="${arr_sorted[@]}"


    fi
done

Vou adivinhar que esta não é a melhor maneira de fazer isso. Eu pensei que eu tentaria implementar tudo sem depender muito de bash builtins, mas agora parece muito sem sentido.

    
por bash learner 11.05.2017 / 06:52

5 respostas

4

Se você tiver permissão para usar dispositivos externos malignos, como sort e cut :

#! /bin/bash
while IFS= read -r line; do
    squeezed=$( tr -d '[:blank:]' <<<"$line" )
    printf '%d\t%d\t%s\n' ${#line} ${#squeezed} "$line"
done | sort -n -k 1 -k 2 | cut -f 3-

Editar: Já que todo mundo está fazendo isso, aqui está uma solução com perl :

perl -e 'print sort { length $a <=> length $b || $a =~ y/ \t//c <=> $b =~ y/ \t//c } <>'
    
por 11.05.2017 / 08:24
4

Pura

sortByLength () 
{ 
    local -a sorted=() sort2
    local line sline sline2 pointer
    while IFS= read -r line; do
        sorted[${#line}]+="$line"
    done
    for pointer in ${!sorted[@]}
    do
        #  ((pointer)) || echo 0: # This will trace empty lines
        sort2=()
        line="${sorted[pointer]}"
        while [ "$line" ]; do
            sline=${line:0:pointer}
            line=${line:pointer}
            sline2=${sline// }
            sort2[${#sline2}]+=${sline}$'\n'
        done
        # echo $pointer:   # This will trace lines length
        printf "%s" "${sort2[@]}"
    done
}

Isso pode ser muito mais rápido, pois não há garfos!

    
por 11.05.2017 / 09:48
3

Usando os mesmos princípios que os outros (obtenha o tamanho da linha, com e sem caracteres de espaço em branco, classifique-os e, em seguida, remova-os), mas com awk :

awk '{NC = length(gensub(/[[:space:]]/, "", "g")); print length, NC, $0}' file |
  sort -nk1,2 |
  sed -r 's/^([0-9]+ ){2}//'

gensub(/[[:space:]]/, "", "g") exclui todos os caracteres de espaços em branco na linha e, em seguida, obtemos o tamanho da string restante

Usando o texto da pergunta até o bloco de códigos, dobrado para 80 caracteres de largura:

$ awk '{NC = length(gensub(/[[:space:]]/, "", "g")); print length, NC, $0}' foo | sort -nk1,2 | sed -r 's/^([0-9]+ ){2}//'


 increasing order).
Here's what I've got so far:
f the idiosyncrasies of bash.
iven a bunch of lines from STDIN, sort them first by the length of the line in i
I've tried this a couple of different ways but I usually get caught up in some o
, sort them by the number of nonblank characters contained in the lines (also in
I am working on learning bash scripting but I am struggling with this problem. G
ncreasing order. Then, if there are any lines with the same number of characters
    
por 11.05.2017 / 08:54
3

Eu não pude resistir em adicionar uma solução sed :

sed 'h;s/.*/0:0;0123456789+/;G;:count
s/\(.\)\(;.*\)\(.\)\(.*\n\)[^[:space:]]/x/
s/\(.\)\(:.*\)\(.\)\(.*\n\).//;:overflow
s/^+/10/;s/:+/:10/;s/\(.\)+\(.*\)\(.\)\(.*\n\)//;t overflow
/\n./b count
G;s/;.*\n/:/' file|sort -t: -n -k 1 -k 2|cut -d: -f 3-

O script sed conta os caracteres e não-blocos e os coloca no início da linha, sort e cut são diretos. E por favor não me diga que isso é um absurdo. Para mim é divertido. (-:

    
por 11.05.2017 / 09:12
3

Função:

sortlen() { while read x ; do \
              y='tr -d '[:blank:]' <<< "$x"' ; echo ${#x} ${#y} "$x" ; \
            done | sort -k 1g,2 -k 2g,3 | cut -d' ' -f3-; }

Teste:

printf "a b c\nabcde\nabcdefg\na\nabcd\n" | sortlen

Saída:

a
abcd
a b c
abcde
abcdefg
    
por 11.05.2017 / 08:31