├── README.md └── shell-utils.sh /README.md: -------------------------------------------------------------------------------- 1 | Some years ago I read a book about shell scripting; AFAIR it was called something like "Teach yourself shell scripting in 2 | 24 hours" by Sams publishing. The author of the book had the (nice) idea of creating a library with shell functions that 3 | can be used over and over by system administrators, shell programmers, and generally users who enjoy typing on a terminal. 4 | 5 | I decided to take the library and extend it. Thus, whenever I find a useful function I add it, and whenever I encounter a bug 6 | I am trying to fix it. I named the library shell-utils because this is what it really is. To use shell-utils simply add it to 7 | your shell's environment. For example, this is how my `.bashrc` entry looks like: 8 | ``` 9 | if [ -f ~/bin/shell-utils.sh ]; then 10 | . ~/bin/shell-utils.sh 11 | fi 12 | ``` 13 | Even though the library contains around fifteen functions, I found out that the most useful (at least if you are working with 14 | many different file types) are `file_to_lower`, `file_to_upper` and `ren_all_suff`, `ren_all_pref`. 15 | -------------------------------------------------------------------------------- /shell-utils.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # shell-utils.sh -- A collection of useful shellscript functions 4 | # Copyright (C) 2005-16 Sakis Kasampalis 5 | 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; either version 2 of the License, or 9 | # (at your option) any later version. 10 | 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program; if not, write to the Free Software 18 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19 | 20 | 21 | # prints an error message to STDERR 22 | # Arguments: $@ -> message to print 23 | perr () 24 | { 25 | printf "ERROR: ${@}\n" >&2 26 | } 27 | 28 | # print a warning nessage to STDERR 29 | # Arguments: $@ -> message to print 30 | pwarn () 31 | { 32 | printf "WARNING: ${@}\n" >&2 33 | } 34 | 35 | # print a usage message and then exits 36 | # Arguments: $@ -> message to print 37 | puse () 38 | { 39 | printf "USAGE: ${@}\n" >&2 40 | } 41 | 42 | # ask a yes/no question 43 | # Arguments: $1 -> The prompt 44 | # $2 -> The default answer (optional) 45 | # Variables: yesno -> set to the user response (y for yes, n for no) 46 | prompt_yn () 47 | { 48 | if [ $# -lt 1 ] 49 | then 50 | puse "prompt_yn prompt [default answer]" 51 | return 1 52 | fi 53 | 54 | def_arg="" 55 | yesno="" 56 | 57 | case "${2}" in 58 | [yY]|[yY][eE][sS]) 59 | def_arg=y ;; 60 | [nN]|[nN][oO]) 61 | def_arg=n ;; 62 | esac 63 | 64 | while : 65 | do 66 | printf "${1} (y/n)? " 67 | test -n "${def_arg}" && printf "[${def_arg}] " 68 | 69 | read yesno 70 | test -z "${yesno}" && yesno="${def_arg}" 71 | 72 | case "${yesno}" in 73 | [yY]|[yY][eE][sS]) 74 | yesno=y ; break ;; 75 | [nN]|[nN][oO]) 76 | yesno=n ; break ;; 77 | *) 78 | yesno="" ;; 79 | esac 80 | done 81 | 82 | export yesno 83 | unset def_arg 84 | } 85 | 86 | # ask a question 87 | # Arguments: $1 -> The prompt 88 | # $2 -> The default answer (optional) 89 | # Variables: response -> set to the user response 90 | prompt_resp () 91 | { 92 | if [ $# -lt 1 ] 93 | then 94 | puse "promp_resp prompt [default answer]" 95 | return 1 96 | fi 97 | 98 | response="" 99 | def_arg="${2}" 100 | 101 | while : 102 | do 103 | printf "${1} ? " 104 | test -n "${def_arg}" -a "${def_arg}" != "-" && printf "[${def_arg}] " 105 | 106 | read response 107 | test -n "${response}" && break 108 | 109 | if [ -z "${response}" -a -n "${def_arg}" ] 110 | then 111 | response="${def_arg}" 112 | break 113 | fi 114 | done 115 | 116 | test "${response}" = "-" && response="" 117 | 118 | export response 119 | unset def_arg 120 | } 121 | 122 | # print a list of process id(s) matching $1 123 | # Arguments: $1 -> the process name to search for 124 | get_pid () 125 | { 126 | if [ $# -lt 1 ] 127 | then 128 | perr "Insufficient Arguments." 129 | return 1 130 | fi 131 | 132 | ps -ef | grep "${1}" | grep -v grep | awk '{ print $2; }' 133 | 134 | unset psopts 135 | } 136 | 137 | # print the numeric user id 138 | # Arguments: $1 -> the user name 139 | get_uid () 140 | { 141 | if [ $# -lt 1 ] 142 | then 143 | perr "Insufficient Arguments." 144 | return 1 145 | fi 146 | 147 | user_id=$(id ${1} 2>/dev/null) 148 | 149 | if [ $? -ne 0 ] 150 | then 151 | perr "No such user: ${1}" 152 | return 1 153 | fi 154 | 155 | printf "${user_id}\n" | sed -e 's/(.*$//' -e 's/^uid=//' 156 | 157 | unset user_id 158 | } 159 | 160 | # print an input string to lower case 161 | # Arguments: $@ -> the string 162 | to_lower () 163 | { 164 | printf "${@}\n" | tr '[A-Z]' '[a-z]' 165 | } 166 | 167 | # print an input string to upper case 168 | # Arguments: $@ -> the string 169 | to_upper () 170 | { 171 | printf "${@}\n" | tr '[a-z]' '[A-Z]' 172 | } 173 | 174 | # convert the input files to lower case 175 | # Arguments: $@ -> files to convert 176 | file_to_lower () 177 | { 178 | for file in "${@}" 179 | do 180 | mv -f "${file}" "$(printf "${file}\n" | tr '[A-Z]' '[a-z]')" \ 181 | 2>/dev/null || perr "File ${file} does not exist" 182 | done 183 | } 184 | 185 | # convert the input files to upper case 186 | # Arguments: $@ -> files to convert 187 | file_to_upper () 188 | { 189 | for file in "${@}" 190 | do 191 | mv -f "${file}" "$(printf "${file}\n" | tr '[a-z]' '[A-Z]')" \ 192 | 2>/dev/null || perr "File ${file} does not exist" 193 | done 194 | } 195 | 196 | # rename all the files with a new suffix 197 | # Arguments: $1 -> the old suffix (for example html) 198 | # $2 -> the new suffix (for example xhtml) 199 | ren_all_suf () 200 | { 201 | if [ $# -lt 2 ] 202 | then 203 | puse "ren_all_suf oldsuffix newsuffix" 204 | return 1 205 | fi 206 | 207 | oldsuffix="${1}" 208 | newsuffix="${2}" 209 | 210 | # fake command to check if the suffix really exists 211 | if ! ls *."${oldsuffix}" 2>/dev/null 212 | then 213 | pwarn "There are no files with the suffix \`${oldsuffix}'." 214 | return 1 215 | fi 216 | 217 | for file in *."${oldsuffix}" 218 | do 219 | newname=$(printf "${file}\n" | sed "s/${oldsuffix}/${newsuffix}/") 220 | mv -i "${file}" "${newname}" 221 | done 222 | 223 | unset oldsuffix newsuffix newname 224 | } 225 | 226 | # rename all the files with a new prefix 227 | # Arguments: $1 -> the old prefix 228 | # $2 -> the new prefix 229 | ren_all_pref () 230 | { 231 | if [ $# -lt 2 ] 232 | then 233 | puse "ren_all_pref oldprefix newprefix" 234 | return 1 235 | fi 236 | 237 | oldprefix="${1}" 238 | newprefix="${2}" 239 | 240 | # fake command to check if the prefix really exists 241 | ls "${oldprefix}"* 2>/dev/null 242 | if ! ls *."${oldprefix}" 2>/dev/null 243 | then 244 | pwarn "There are no files with the prefix \`${oldprefix}'." 245 | return 1 246 | fi 247 | 248 | for file in "${oldprefix}"* 249 | do 250 | newname=$(printf "${file}\n" | sed "s/${oldprefix}/${newprefix}/") 251 | mv -i "${file}" "${newname}" 252 | done 253 | 254 | unset oldprefix newprefix newname 255 | } 256 | 257 | # convert a list of dos formatted files to the POSIX format 258 | # Arguments: $@ -> the list of files to convert 259 | dos2posix () 260 | { 261 | for file in "${@}" 262 | do 263 | tr -d '\015' < "${file}" > "${file}".posix 264 | prompt_yn "Overwrite ${file}" 265 | test "${yesno}" = "y" && mv -f "${file}".posix "${file}" 266 | done 267 | } 268 | 269 | # print the system's name 270 | os_name () 271 | { 272 | case $(uname -s) in 273 | *BSD) 274 | printf BSD ;; 275 | Darwin) 276 | printf macOS ;; 277 | SunOS) 278 | case $(uname -r) in 279 | 5.*) printf Solaris ;; 280 | *) printf SunOS ;; 281 | esac 282 | ;; 283 | Linux) 284 | printf GNU/Linux ;; 285 | MINIX*) 286 | printf MINIX ;; 287 | HP-UX) 288 | echo HPUX ;; 289 | AIX) 290 | echo AIX ;; 291 | *) echo unknown ;; 292 | esac 293 | printf "\n" 294 | } 295 | 296 | # print out the number of characters which exist in a file 297 | # Arguments: $@ -> the files to count the chars of 298 | chars () 299 | { 300 | case $(os_name) in 301 | bsd|sunos|linux) 302 | wcopt="-c" ;; 303 | *) 304 | wcopt="-m" ;; 305 | esac 306 | 307 | wc "${wcopt}" "${@}" 308 | 309 | unset wcopt 310 | } 311 | 312 | # insert quotes in the beggining and the end of each file's line 313 | # Arguments: $1 -> the file of which the contents will be quoted 314 | ins_quotes () 315 | { 316 | if [ $# -ne 1 ] 317 | then 318 | puse "ins_quotes file" 319 | return 1 320 | fi 321 | 322 | if [ ! -f "${1}" ] 323 | then 324 | perr "Argument must be a file." 325 | return 1 326 | fi 327 | 328 | while read ln 329 | do 330 | ln=\"$ln\" 331 | printf "${ln}\n" 332 | done < "${1}" 333 | } 334 | 335 | # remove all the files of a specific type that exist in the current directory 336 | # Arguments: $1 -> the string to search in the output of `file' 337 | # NOTE: use with caution... 338 | rm_all () 339 | { 340 | if [ $# -ne 1 ] 341 | then 342 | puse "rm_all wildcard" 343 | return 1 344 | fi 345 | 346 | file * | grep "${1}" | awk '{ print $1 }' | sed 's/://' | xargs rm 347 | } 348 | 349 | # verbose remove 350 | # Arguments: $@ -> what to remove 351 | rm () 352 | { 353 | /bin/rm -i "${@}" 354 | } 355 | 356 | # listing with colours by default 357 | # Arguments: $@ -> what to list 358 | ls () 359 | { 360 | case $(os_name) in 361 | bsd|macOS) 362 | lsopt="-G" ;; 363 | *) 364 | lsopt="-c" ;; 365 | esac 366 | 367 | /bin/ls "${lsopt}" "${@}" 368 | } 369 | 370 | # long listing 371 | # Arguments: $@ -> what to list 372 | ll () 373 | { 374 | ls -lh "${@}" 375 | } 376 | 377 | # list all files 378 | # Arguments: $@ -> what to list 379 | la () 380 | { 381 | ls -A "${@}" 382 | } 383 | 384 | # list by column and type 385 | # Arguments: $@ -> what to list 386 | l () 387 | { 388 | ls -CF "${@}" 389 | } 390 | 391 | # grep with colours by default 392 | # Arguments: $@ -> what to match 393 | grep () 394 | { 395 | /usr/bin/grep --color=auto "${@}" 396 | } 397 | 398 | # fgrep with colours by default 399 | # Arguments: $@ -> what to match 400 | fgrep () 401 | { 402 | /usr/bin/fgrep --color=auto "${@}" 403 | } 404 | 405 | # egrep with colours by default 406 | # Arguments: $@ -> what to match 407 | egrep () 408 | { 409 | /usr/bin/egrep --color=auto "${@}" 410 | } 411 | 412 | # verbose move/rename 413 | # Arguments: $@ -> what to match 414 | mv () 415 | { 416 | /bin/mv -i "${@}" 417 | } 418 | 419 | # verbose copy 420 | # Arguments: $@ -> what to match 421 | cp () 422 | { 423 | /bin/cp -i "${@}" 424 | } 425 | 426 | # copy with progress using rsync 427 | pcp () 428 | { 429 | rsync --progress -ah "${@}" 430 | } 431 | 432 | # make a file executable 433 | # Arguments: $@ -> what to match 434 | cx () 435 | { 436 | /bin/chmod +x "${@}" 437 | } 438 | 439 | # count lines 440 | # Arguments: $@ -> what to match 441 | cl () 442 | { 443 | /usr/bin/wc -l "${@}" 444 | } 445 | 446 | # sort files 447 | # Arguments: $@ -> what to match 448 | fsort () 449 | { 450 | ls -lSh "${@}" 2>/dev/null | tail +2 | awk '{print $5 "\t" $9}' 451 | } 452 | 453 | # sort mixed (directories & files) 454 | # Arguments: $@ -> what to match 455 | dsort () 456 | { 457 | du -s "${@}" 2>/dev/null | sort -rn | awk '{print $2}' | xargs du -sh 2>/dev/null 458 | } 459 | 460 | # simple way to keep a backup of a file 461 | # Arguments: $1 -> the file 462 | bkup () 463 | { 464 | if [ $# -ne 1 ] 465 | then 466 | puse "bkup file" 467 | return 1 468 | fi 469 | 470 | file_copy=${1}.$(date +%Y%m%d.%H%M.ORIG) 471 | mv -f ${1} ${file_copy} 472 | printf "Backing up ${1} to ${file_copy}\n" 473 | cp -p "${file_copy}" "${1}" 474 | 475 | unset file_copy 476 | } 477 | 478 | # show a message near the mouse 479 | # useful for things like ``./build ; msg "libc build"'' 480 | # Arguments: $1 -> the message 481 | msg () 482 | { 483 | type xmessage 2>/dev/null 484 | 485 | if [ $? -ne 0 ] 486 | then 487 | perr "xmessage is required, please install it." 488 | return 1 489 | fi 490 | 491 | if [ $# -ne 1 ] 492 | then 493 | puse "msg 'my message'" 494 | return 1 495 | fi 496 | 497 | test $? -eq 0 && out=success 498 | out=${out-failure} 499 | 500 | msg="${1}: ${out}" 501 | 502 | xmessage -buttons ok -default ok -nearmouse "${msg}" 2>/dev/null 503 | 504 | unset out err msg 505 | } 506 | 507 | # print a specific line of a file 508 | # Arguments: $1 -> the line number 509 | # $2 -> the file 510 | pln () 511 | { 512 | if [ $# -ne 2 ] 513 | then 514 | puse "pln line file" 515 | return 1 516 | fi 517 | 518 | sed -n "${1}p" ${2} 519 | } 520 | 521 | # create a directory and enter it 522 | # Arguments: $1 -> the directory name 523 | mkcd () 524 | { 525 | if [ $# -ne 1 ] 526 | then 527 | puse "mkcd directory" 528 | return 1 529 | fi 530 | 531 | mkdir "${1}" && cd "${1}" 532 | } 533 | 534 | # list all the files that are newer than the given 535 | # Arguments: $1 -> the file name 536 | newer() 537 | { 538 | if [ $# -ne 1 ] 539 | then 540 | puse "newer file" 541 | return 1 542 | fi 543 | 544 | ls -t | sed "/^${1}\$/q" | grep -v "${1}" 545 | } 546 | 547 | # list all the files that are older than the given 548 | # Arguments: $1 -> the file name 549 | older() 550 | { 551 | if [ $# -ne 1 ] 552 | then 553 | puse "older file" 554 | return 1 555 | fi 556 | 557 | ls -tr | sed "/^${1}\$/q" | grep -v "${1}" 558 | } 559 | 560 | # detect double words (eg. "hello my my friend") 561 | # Arguments: $1 -> the file(s) to be checked 562 | dword() 563 | { 564 | if [ $# -ne 1 ] 565 | then 566 | puse "dword file" 567 | return 1 568 | fi 569 | 570 | awk ' 571 | FILENAME != prev { 572 | NR = 1 573 | prev = FILENAME 574 | } 575 | NF > 0 { 576 | if ($1 == lastword) 577 | printf "%s:%d:`%s`\n", FILENAME, NR, $1 578 | for (i = 2; i <= NF; i++) 579 | if ($i == $(i-1) ) 580 | printf "%s:%d:`%s`\n", FILENAME, NR, $i 581 | if (NF > 0) 582 | lastword = $NF 583 | }' "${@}" 584 | } 585 | 586 | # count word frequencies 587 | # Arguments: $1 -> the file(s) to use while counting 588 | wfreq() 589 | { 590 | if [ $# -ne 1 ] 591 | then 592 | puse "wfreq file" 593 | return 1 594 | fi 595 | 596 | awk ' 597 | { 598 | for (i = 1; i <= NF; i++) 599 | cnt[$i]++ 600 | } 601 | END { 602 | for (w in cnt) 603 | print w, cnt[w] 604 | }' "${@}" 605 | } 606 | 607 | # get the numeric value of a month (eg. march = 3) 608 | # Arguments: $1 -> the month's name 609 | # Variables: nmonth -> set to the value of the month 610 | n_month() 611 | { 612 | if [ $# -ne 1 ] 613 | then 614 | puse "n_month month" 615 | return 1 616 | fi 617 | 618 | nmonth=0 619 | 620 | case $1 in 621 | jan*) nmonth=1 ;; 622 | feb*) nmonth=2 ;; 623 | mar*) nmonth=3 ;; 624 | apr*) nmonth=4 ;; 625 | may*) nmonth=5 ;; 626 | jun*) nmonth=6 ;; 627 | jul*) nmonth=7 ;; 628 | aug*) nmonth=8 ;; 629 | sep*) nmonth=9 ;; 630 | oct*) nmonth=10 ;; 631 | nov*) nmonth=11 ;; 632 | dec*) nmonth=12 ;; 633 | *) perr "Incorrect month." ; return 1 ;; 634 | esac 635 | 636 | export nmonth 637 | } 638 | 639 | # /usr/bin/cal improved 640 | # examples: `cal y' shows the full current year 641 | # `cal mar' shows march of the current year 642 | # `cal apr - jun' shows april-june of the current year (note the spaces) 643 | cal() 644 | { 645 | cyear=$(date | awk '{ print $4 }') # current year 646 | 647 | case $# in 648 | 1) 649 | case $1 in 650 | [yY]*) year=$cyear ;; 651 | jan*|feb*|mar*|apr*|may*|jun*|jul*|aug*|sep*|oct*|nov*|dec*) month=$1; year=$cyear ;; 652 | *) year=$1 ;; 653 | esac ;; 654 | 2) month=$1; year=$2 ;; 655 | 3) month=$1; month2=$3 ; year=$cyear ;; # eg. cal mar - jun 656 | *) month=$1; year=$2 ;; 657 | esac 658 | 659 | if [ -z $month2 ] ; then 660 | /usr/bin/cal $month $year 661 | else # assume month range 662 | # TODO: replace (with an array?) and loop to avoid code duplication 663 | n_month $month 664 | test $? -eq 0 && m1=$nmonth 665 | n_month $month2 666 | test $? -eq 0 && m2=$nmonth 667 | for m in $(seq $m1 $m2) ; do 668 | /usr/bin/cal $m $year 669 | done 670 | fi 671 | 672 | unset cyear year month month2 673 | } 674 | 675 | --------------------------------------------------------------------------------