#!/bin/bash # Copyright 2018 Juliano Santos [SHAMAN] # # This file is part of bashsrc. # # bashsrc is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # bashsrc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with bashsrc. If not, see . [ -v __STRING_SH__ ] && return 0 readonly __STRING_SH__=1 source builtin.sh # .FUNCTION string.len -> [uint]|[bool] # # Retorna o comprimento de 'expr'. # function string.len() { getopt.parse 1 "expr:str:$1" "${@:2}" echo ${#1} return $? } # .FUNCTION string.capitalize -> [str]|[bool] # # Retorna uma cópia em letras maiúsculas de 'expr', ou seja, torna o primeiro # caractere em maiúsculo e o restante em minúsculos. # function string.capitalize() { getopt.parse 1 "expr:str:$1" "${@:2}" local sub=${1:1} local ini=${1:0:1} echo "${ini^}${sub,,}" return $? } # .FUNCTION string.center <[uint]width> -> [str]|[bool] # # Retorna uma cópia de 'expr' centralizando o texto. # function string.center() { getopt.parse 3 "expr:str:$1" "fillchar:char:$2" "width:uint:$3" "${@:4}" local ch cr local lc=$(($3-${#1})) ((lc > 0)) && printf -v ch '%*s' $((lc/2)) (((lc % 2) == 1)) && cr=$2 ch=${ch// /$2} echo "${ch}${1}${ch}${cr}" return $? } # .FUNCTION string.count -> [uint]|[bool] # # Retorna 'N' ocorrências de 'sub' em 'expr'. # function string.count() { getopt.parse 2 "expr:str:$1" "sub:str:$2" "${@:3}" local expr=$1 local c while [[ $2 && $expr =~ $2 ]]; do ((c++)); expr=${expr/$BASH_REMATCH/} done echo ${c:-0} return $? } # .FUNCTION string.endswith -> [bool] # # Retorna 'true' se 'expr' termina com 'suffix, caso contrário 'false'. # function string.endswith() { getopt.parse 2 "expr:str:$1" "suffix:str:$2" "${@:3}" [[ $1 =~ $2$ ]] return $? } # .FUNCTION string.startswith -> [bool] # # Retorna 'true' se 'expr' inicia com 'prefix', caso contrário 'false'. # function string.startswith() { getopt.parse 2 "expr:str:$1" "prefix:str:$2" "${@:3}" [[ $1 =~ ^$2 ]] return $? } # .FUNCTION string.expandspace -> [bool] # # Retorna uma sequência caracteres em que os espaços são expandidos # ao comprimento especificado em 'size'. # function string.expandspaces() { getopt.parse 2 "expr:str:$1" "size:str:$2" "${@:3}" local spc printf -v spc '%*s' $2 echo "${1// /$spc}" return $? } # .FUNCTION string.find -> [int]|[bool] # # Retorna o índice mais baixo da ocorrência de 'sub' em 'expr'. # Se não houver correspondência é retornado '-1'. # function string.find() { getopt.parse 2 "expr:str:$1" "sub:str:$2" "${@:3}" local pos sub sub=${1#*$2} pos=$((${#1}-${#sub}-${#2})) ((pos < 0)) && pos=-1 echo $pos return $? } # .FUNCTION string.rfind -> [int]|[bool] # # Retorna o índice mais alto da ocorrência de 'sub' em 'expr'. # Se não houver correspondência é retornado '-1'. # function string.rfind() { getopt.parse 2 "expr:str:$1" "sub:str:$2" "${@:3}" local pos sub sub=${1##*$2} pos=$((${#1}-${#sub}-${#2})) ((pos < 0)) && pos=-1 echo $pos return $? } # .FUNCTION string.isalnum -> [bool] # # Retorna 'true' se 'expr' contém letras e dígitos. # function string.isalnum() { getopt.parse 1 "expr:str:$1" "${@:2}" [[ $1 == +([[:alnum:]]) ]] return $? } # .FUNCTION string.isalpha -> [bool] # # retorna 'true' se 'expr' contém somente letras. # function string.isalpha() { getopt.parse 1 "expr:str:$1" "${@:2}" [[ $1 == +([[:alpha:]]) ]] return $? } # .FUNCTION string.isdigit -> [bool] # # Retorna 'true' se 'expr' contém somente dígitos. # function string.isdigit() { getopt.parse 1 "expr:str:$1" "${@:2}" [[ $1 == +([[:digit:]]) ]] return $? } # .FUNCTION string.isspace -> [bool] # # Retorna 'true' se 'expr' contém somente espaços. # function string.isspace() { getopt.parse 1 "expr:str:$1" "${@:2}" [[ $1 == +([[:space:]]) ]] return $? } # .FUNCTION string.isprint -> [bool] # # Retorna 'true' se 'expr' contém somente caracteres imprimíveis. # function string.isprint() { getopt.parse 1 "expr:str:$1" "${@:2}" [[ $1 == +([[:print:]]) ]] return $? } # .FUNCTION string.islower -> [bool] # # Retorna 'true' se 'expr' contém somente caracteres minúsculos. # function string.islower() { getopt.parse 1 "expr:str:$1" "${@:2}" [[ $1 == +([^[:upper:]]) ]] return $? } # .FUNCTION string.isupper -> [bool] # # Retorna 'true' se 'expr' contém somente caracteres maiúsculos. # function string.isupper() { getopt.parse 1 "expr:str:$1" "${@:2}" [[ $1 == +([^[:lower:]]) ]] return $? } # .FUNCTION string.istitle -> [bool] # # Retorna 'true' se 'expr' é uma string de titulo e há pelo menos um # caractere em maiúsculo, ou seja, caracteres maiúsculos só podem seguir sem # caracteres e caracteres minúsculos apenas os maiúsculos. Retorna 'false' # de outra forma. # function string.istitle() { getopt.parse 1 "expr:str:$1" "${@:2}" [[ $1 == +(*([^[:alpha:]])@([[:upper:]])+([[:lower:]])) ]] return $? } # .FUNCTION string.join [str]|[bool] # # Retorna uma string que é concatenação dos elementos iteráveis delimitados por 'sep'. # function string.join() { getopt.parse 2 "expr:str:$1" "sep:str:$2" "${@:3}" local iter mapfile -t iter <<< "$1" printf -v expr "%s${2//%/%%}" "${iter[@]}" echo "${expr%$2}" return $? } # .FUNCTION string.ljust -> [str]|[bool] # # Retorna uma string justificada à esquerda em uma cadeia de largura de comprimento. # O preenchimento é feito usando o caractere de preenchimento especificado em 'fillchar'. # function string.ljust() { getopt.parse 3 "expr:str:$1" "fillchar:char:$2" "width:uint:$3" "${@:4}" local ch wd wd=$(($3-${#1})) printf -v ch '%*s' $(($wd > 0 ? $wd : 0)) echo "${ch// /$2}${1}" return $? } # .FUNCTION string.rjust -> [str]|[bool] # # Retorna uma string justificada à diretia em uma cadeia de largura de comprimento. # O preenchimento é feito usando o caractere de preenchimento especificado em 'fillchar'. # function string.rjust() { getopt.parse 3 "expr:str:$1" "fillchar:char:$2" "width:uint:$3" "${@:4}" local ch wd wd=$(($3-${#1})) printf -v ch '%*s' $(($wd > 0 ? $wd : 0)) echo "${1}${ch// /$2}" return $? } # .FUNCTION string.lower -> [str]|[bool] # # Converte a cadeia de caracteres para minúsculo. # function string.lower() { getopt.parse 1 "expr:str:$1" "${@:2}" echo "${1,,}" return $? } # .FUNCTION string.upper -> [str]|[bool] # # Converte a cadeia de caracteres para maiúsculo. # function string.upper() { getopt.parse 1 "expr:str:$1" "${@:2}" echo "${1^^}" return $? } # .FUNCTION string.strip -> [str]|[bool] # # Retorna uma cópia da string removendo a substring do inicio e final # da cadeia de caracteres. # function string.strip() { getopt.parse 2 "expr:str:$1" "sub:str:$2" "${@:3}" local on expr shopt -q extglob && on='s' shopt -s extglob expr=${1##+($2)} echo "${expr%%+($2)}" shopt -${on:-u} extglob return $? } # .FUNCTION string.lstrip -> [str]|[bool] # # Retorna uma cópia da string removendo a substring do inicio da # cadeia de caracteres. # function string.lstrip() { getopt.parse 2 "expr:str:$1" "sub:str:$2" "${@:3}" local on shopt -q extglob && on='s' shopt -s extglob echo "${1##+($2)}" shopt -${on:-u} extglob return $? } # .FUNCTION string.rstrip -> [str]|[bool] # # Retorna uma cópia da string removendo a substring do final da # cadeia de caracteres. # function string.rstrip() { getopt.parse 2 "expr:str:$1" "sub:str:$2" "${@:3}" local on shopt -q extglob && on='s' shopt -s extglob echo "${1%%+($2)}" shopt -${on:-u} extglob return $? } # .FUNCTION string.replace -> [str]|[bool] # # Retorna uma cópia da string substituindo 'N' ocorrências de 'old' por 'new'. # function string.replace { getopt.parse 4 "expr:str:$1" "old:str:$2" "new:str:$3" "count:int:$4" "${@:5}" local expr c i expr=$1 for ((i=0; i < ${#expr}; i++)); do if [[ ${expr:$i:${#2}} == $2 ]]; then expr=${expr:0:$i}${3}${expr:$(($i+${#2}))} i=$(($i+${#3})) [[ $((++c)) -eq $4 ]] && break fi done echo "$expr" return $? } # .FUNCTION string.fnreplace ... -> [str]|[bool] # # Retorna uma cópia da string substituindo 'N' ocorrências de 'old' pelo retorno da função. # A função é chamada a cada ocorrência, passando como argumento posicional '$1' a expressão # casada com 'N' args (opcional). # # == EXEMPLO == # # source string.sh # # texto='Linux é vida, Linux é liberdade, Linux é tudo!!' # # # Função que remove os dois primeiros caracteres da expressão. # rm_chars(){ # echo "${1#??}" # } # # # Manipulando a palavra 'Linux' utilizando funções já existentes. # string.fnreplace "$texto" 'Linux' -1 string.reverse # string.fnreplace "$texto" 'Linux ' -1 string.repeat 3 # string.fnreplace "$texto" 'Linux' 2 string.upper # # # Função personalizada. # string.fnreplace "$texto" 'Linux' -1 rm_chars # # == SAÍDA == # # xuniL é vida, xuniL é liberdade, xuniL é tudo!! # Linux Linux Linux é vida, Linux Linux Linux é liberdade, Linux Linux Linux é tudo!! # LINUX é vida, LINUX é liberdade, Linux é tudo!! # nux é vida, nux é liberdade, nux é tudo!! # function string.fnreplace { getopt.parse -1 "expr:str:$1" "old:str:$2" "count:int:$3" "func:function:$4" "args:str:$5" ... "${@:6}" local expr fn i c expr=$1 for ((i=0; i < ${#expr}; i++)); do if [[ ${expr:$i:${#2}} == $2 ]]; then fn=$($4 "$2" "${@:5}") expr=${expr:0:$i}${fn}${expr:$(($i+${#2}))} i=$(($i+${#fn})) [[ $((++c)) -eq $3 ]] && break fi done echo "$expr" return $? } # .FUNCTION string.replacers ... -> [str]|[bool] # # Retorna uma cópia da string substituindo todas as ocorrências de 'old' por 'new', # podendo ser especificado mais de um conjunto de substituição. # # == EXEMPLO == # # source string.sh # # texto='A Microsoft além do Windows agora tem sua própria distro Linux'. # # # Substituições. # string.replacers "$texto" 'Windows' 'Ruindows' 'Microsoft' 'Micro$oft' 'Linux' 'Rindux' # # == SAÍDA == # # A Micro$oft além do Ruindows agora tem sua própria distro Rindux. # function string.replacers() { getopt.parse -1 "expr:str:$1" "old:str:$2" "new:str:$3" ... "${@:4}" local expr=$1 set "${@:2}" while [[ $1 && $expr == *$1* ]]; do expr=${expr//$1/$2} shift 2 done echo "$expr" return $? } # .FUNCTION string.split -> [str]|[bool] # # Retorna uma lista de palavras delimitadas por 'sep' em 'count' vezes. # Se 'count' for menor que zero aplica a ação em todas as ocorrências. # # == EXEMPLO == # # source string.sh # # distros='Slackware,Debian,Centos,Ubuntu,Manjaro' # # string.split "$distro" ',' -1 # echo --- # string.split "$distro" ',' 2 # # == SAÍDA == # # Slackware # Debian # Centos # Ubuntu # Manjaro # --- # Slackware # Debian # Centos,Ubuntu,Manjaro # function string.split() { getopt.parse 3 "expr:str:$1" "sep:str:$2" "count:int:$3" "${@:4}" local c expr=$1 while [[ $expr == *$2* ]]; do [[ $((c++)) -eq $3 ]] && break expr=${expr/$2/$'\n'} done mapfile -t expr <<< "$expr" printf '%s\n' "${expr[@]}" return $? } # .FUNCTION string.swapcase -> [str]|[bool] # # Retorna uma cópia de 'expr' convertendo os caracteres minúsculos para # maiúsculos e vice-versa. # function string.swapcase() { getopt.parse 1 "expr:str:$1" "${@:2}" echo "${1~~}" return $? } # .FUNCTION string.title -> [str]|[bool] # # Retorna uma cópia titulada de 'expr', ou seja, as palavras começam com # a primeira letra maiúscula e as demais minúsculas. # function string.title() { getopt.parse 1 "expr:str:$1" "${@:2}" local expr=${1,,} while [[ $expr =~ [^a-zA-Z][a-z] ]]; do expr=${expr/$BASH_REMATCH/${BASH_REMATCH^^}} done echo "${expr^}" return $? } # .FUNCTION string.reverse -> [str]|[bool] # # Retorna uma cópia invertida da sequẽncia de caracteres de 'expr'. # function string.reverse() { getopt.parse 1 "expr:str:$1" "${@:2}" rev <<< "$1" return $? } # .FUNCTION string.repeat -> [str]|[bool] # # Retorna uma copia de 'expr' repetida 'N' vezes. # function string.repeat() { getopt.parse 2 "expr:str:$1" "count:uint:$2" "${@:3}" local i for ((i=0; i < $2; i++)); do echo -n "$1" done; echo return $? } # .FUNCTION string.zfill -> [str]|[bool] # # Preenche a expressão com 'N' zeros a esquerda. # function string.zfill() { getopt.parse 2 "expr:str:$1" "witdh:uint:$2" "${@:3}" local i for ((i=0; i < $2; i++)); do echo -n '0' done; echo "$1" return $? } # .FUNCTION string.compare -> [bool] # # Compara as expressões e retorna 'true' se forem iguais, caso contrário 'false'. # Se 'case' for igual a 'true' ativa a análise tipográfica de diferenciação entre # caracteres maiúsculos e minúsculos. # # == EXEMPLO == # # source string.sh # # string.compare 'Linux' 'LINUX' true && echo true || echo false # string.compare 'Linux' 'LINUX' false && echo true || echo false # # == SAÍDA == # # false # true # function string.compare() { getopt.parse 3 "expr1:str:$1" "expr2:str:$2" "case:bool:$3" "${@:4}" if $3; then [ "$1" == "$2" ]; else [ "${1,,}" == "${2,,}" ]; fi return $? } # .FUNCTION string.contains -> [bool] # # Retorna 'true' se a expressão contém a substring. # function string.contains() { getopt.parse 2 "expr:str:$1" "sub:str:$2" "${@:3}" [[ $1 == *$2* ]] return $? } # .FUNCTION string.map -> [char]|[uint] # # Retorna uma lista iterável da cadeia de caracteres. # function string.map() { getopt.parse 1 "expr:str:$1" "${@:2}" local i for ((i=0; i < ${#1}; i++)); do echo "${1:$i:1}" done return $? } # .FUNCTION string.fnsmap ... -> [str]|[bool] # # Aplica a função em cada substring delimitada pelo caractere ' ' espaço contido na expressão # substituindo-a pelo retorno da função. # # == EXEMPLO == # # source string.sh # # texto='Linux é sinônimo de liberdade e ser livre é uma questão de escolha.' # # flag(){ # echo "[$1]" # } # # char(){ # echo "[${1:0:1}]" # } # # string.fnsmap "$texto" flag # string.fnsmap "$texto" char # string.fnsmap "$texto" string.repeat 2 # # == SAÍDA == # # [Linux] [é] [sinônimo] [de] [liberdade] [e] [ser] [livre] [é] [uma] [questão] [de] [escolha.] # [L] [é] [s] [d] [l] [e] [s] [l] [é] [u] [q] [d] [e] # LinuxLinux éé sinônimosinônimo dede liberdadeliberdade ee serser livrelivre éé umauma questãoquestão dede escolha.escolha. # function string.fnsmap() { getopt.parse -1 "expr:str:$1" "func:function:$2" "args:str:$3" ... "${@:4}" local expr str while IFS=$'\n' read -r str; do expr+=$($2 "$str" "${@:3}")' ' done < <(printf '%s\n' $1) echo "$expr" return $? } # .FUNCTION string.fncmap ... -> [str]|[bool] # # Aplica a função em cada caractere da expressão substituindo-o pelo retorno da função. # # == EXEMPLO == # # source string.sh # # texto='Viva o Linux' # # func(){ # echo "($1)" # } # # string.fncmap "$texto" func # # == SAÍDA == # # (V)(i)(v)(a)( )(o)( )(L)(i)(n)(u)(x) # function string.fncmap() { getopt.parse -1 "expr:str:$1" "func:function:$2" "args:str:$3" ... "${@:4}" local expr i for ((i=0; i < ${#1}; i++)); do expr+=$($2 "${1:$i:1}" "${@:3}") done echo "$expr" return $? } # .FUNCTION string.filter ... -> [str]|[bool] # # Filtra a expressão retornando apenas a cadeia de caracteres representada # pela classe. Pode ser especificada mais de uma classe. # # Classes suportadas: # # [:alnum:] - todas as letras e dígitos # [:alpha:] - todas as letras # [:blank:] - todos os espaços brancos na horizontal # [:cntrl:] - todos os caracteres de controle # [:digit:] - todos os dígitos # [:graph:] - todos os caracteres exibíveis, exceto espaços # [:lower:] - todas as letras minúsculas # [:print:] - todos os caracteres exibíveis, inclusive espaços # [:punct:] - todos os caracteres de pontuação # [:space:] - todos os espaços brancos na horizontal ou vertical # [:upper:] - todas as letras maiúsculas # [:xdigit:] - todos os dígitos hexadecimais # # == EXEMPLO == # # source string.sh # # distro='Ubuntu 16.04, Debian 9, Slackware 14' # # string.filter "$distro" [:alpha:] [:space:] # string.filter "$distro" [:digit:] # string.filter "$distro" [:punct:] # # == SAÍDA == # # Ubuntu Debian Slackware # 1604914 # .,, # function string.filter() { getopt.parse -1 "expr:str:$1" "class:str:$2" ... "${@:3}" echo "${1//[^${@:2}]/}" return $? } # .FUNCTION string.field ... [str]|[bool] # # Retorna 'N' campos delimitados pela substring. Utilize notação negativa para # captura reversa dos campos, ou seja, se o índice for igual à '-1' é retornado # o útlimo elemento, '-2' o penúltimo e assim por diante. # # == EXEMPLO == # # source string.sh # # lista='item1,item2,item3,item4,item5' # # string.field "$lista" ',' {1..3} # string.field "$lista" ',' 1 4 # string.field "$lista" ',' {2..5} # string.field "$lista" ',' -1 # string.field "$lista" 'item3' 1 # # == SAÍDA == # # item1 item2 item3 # item1 item4 # item2 item3 item4 item5 # item5 # item1,item2, # function string.field() { getopt.parse -1 "expr:str:$1" "sep:str:$2" "field:int:$3" ... "${@:4}" local field fields expr mapfile -t fields <<< "${1//$2/$'\n'}" for field in ${@:3}; do expr+=${fields[$((field > 0 ? field - 1 : field))]}' ' done echo "$expr" return $? } # .FUNCTION string.slice -> [str]|[bool] # # Retorna uma substring resultante do intervalo dentro de uma cadeia # de caracteres. O slice é a represetação do intervalo a ser capturado # e precisa respeitar o seguinte formato: # # [start:len]... # # start - Posição inicial dentro da cadeia. # len - Comprimento a ser capturado a partir de 'start'. # # > Não pode conter espaços entre slices. # > Utilize notação negativa para captura reversa. # # Pode ser especificado mais de um slice dentro da mesma expressão, # onde o slice subsequente trata a cadeia resultante do slice anterior # e assim respecitivamente. # # == EXEMPLO == # # source string.sh # # texto='Programação com shell script' # # string.slice "$texto" '[16:]' # string.slice "$texto" '[:11]' # string.slice "$texto" '[:-6]' # string.slice "$texto" '[-1]' # string.slice "$texto" '[4:10][2:9][:-2]' # # == SAÍDA == # # shell script # Programação # Programação com shell # t # mação # function string.slice() { getopt.parse 2 "expr:str:$1" "slice:str:$2" "${@:3}" [[ $2 =~ ${__BUILTIN__[slice]} ]] || error.fatal "'$2' erro de sintaxe na expressão slice" local str=$1 local slice=$2 local ini len while [[ $slice =~ \[([^]]+)\] ]]; do IFS=':' read ini len <<< "${BASH_REMATCH[1]}" [[ ${len#-} -gt ${#str} ]] && str='' && break [[ ${BASH_REMATCH[1]} != *@(:)* ]] && len=1 ini=${ini:-0} len=${len:-$((${#str}-$ini))} str=${str:$ini:$len} slice=${slice/\[${BASH_REMATCH[1]}\]/} done echo "$str" return $? } # .TYPE string_t # # Implementa o objeto 'S' com os métodos: # # S.len # S.capitalize # S.center # S.count # S.endswith # S.startswith # S.expandspaces # S.find # S.rfind # S.isalnum # S.isalpha # S.isspace # S.isprint # S.islower # S.isupper # S.istitle # S.join # S.ljust # S.rjust # S.lower # S.upper # S.strip # S.lstrip # S.rstrip # S.replace # S.fnreplace # S.replacers # S.split # S.swapcase # S.title # S.reverse # S.repeat # S.zfill # S.compare # S.contains # S.fnsmap # S.fncmap # S.filter # S.field # S.slice # typedef string_t \ string.len \ string.capitalize \ string.center \ string.count \ string.endswith \ string.startswith \ string.expandspaces \ string.find \ string.rfind \ string.isalnum \ string.isalpha \ string.isspace \ string.isprint \ string.islower \ string.isupper \ string.istitle \ string.join \ string.ljust \ string.rjust \ string.lower \ string.upper \ string.strip \ string.lstrip \ string.rstrip \ string.replace \ string.fnreplace \ string.replacers \ string.split \ string.swapcase \ string.title \ string.reverse \ string.repeat \ string.zfill \ string.compare \ string.contains \ string.map \ string.fnsmap \ string.fncmap \ string.filter \ string.field \ string.slice # Funções readonly -f string.len \ string.capitalize \ string.center \ string.count \ string.endswith \ string.startswith \ string.expandspaces \ string.find \ string.rfind \ string.isalnum \ string.isalpha \ string.isspace \ string.isprint \ string.islower \ string.isupper \ string.istitle \ string.join \ string.ljust \ string.rjust \ string.lower \ string.upper \ string.strip \ string.lstrip \ string.rstrip \ string.replace \ string.fnreplace \ string.replacers \ string.split \ string.swapcase \ string.title \ string.reverse \ string.repeat \ string.zfill \ string.compare \ string.contains \ string.map \ string.fnsmap \ string.fncmap \ string.filter \ string.field \ string.slice # /* __STRING_SH__ */