ZNHOO Whatever you are, be a good one!

Snippets

  1. ABC
  2. disk usage
  3. arithmatic operation
  4. if string comparision
  5. apg
  6. dig
  7. find
  8. Bulk rename
  9. Number range
  10. netstat
  11. ln
  12. Encoding/charset conversion
  13. CR-LF/LF
    1. Detect line ending:
    2. Conversion
  14. BOM
  15. Remove whitespace
  16. Empty line
  17. newline
  18. read line by line
  19. darkhttpd
  20. SSL cert
  21. curl/wget
  22. mtr/ping
  23. Image crop and keep aspect ratio

ABC

cat -e input.file
sed -n l input.file

disk usage

# disk usage
du -hsc -- file1 dir1
du -hsc -- *
# disk free
df -h

arithmatic operation

echo 5.3025^2*4.871 | bc
bc <<< '100*0.13'
awk "BEGIN {print 100/3.5}"

if string comparision

if [[ "$str1" == "$str2" ]]; then
  echo "$str1 equals to $str2"
elif [[ $str1 == *"$str2"* ]]; then
  echo "$str1 contains $str2"
else
  echo "$str1 has nothing to do with $str2"
fi

Pay attention to quote.

apg

~ # apg -a1 -n1 -m16 -MSNCL -k

dig

~ # dig @dns-server -p 53 www.example.com

find

$ find -L /path/to/search -type f -iname "*filename*"
$ find -L /path/to/search -type f -iname '*filename*'
$ find -L /path/to/search -type f -iname '*.apk' -printf "%f\n" -exec cp -fv '{}' /path/to/copy \;
$ find APPs/ -maxdepth 1 -type f -iname '*.apk' -exec bash -c 'for file; do file="${file##*/}"; mkdir -p ROM/system/preset_apps/"${file%.apk}"; cp APPs/"${file}" ROM/system/preset_apps/"${file%.*}"; done' bash '{}' +
$ find -P history/ -type f -name '*.HEIC' -exec bash -c 'for file; do mv -v "$file" "${file%%.*}.heic"; done' 'bash' {} +
$ find ROM/system/media/wallpaper/ -depth -type f ! -path '*/wallpaper_15.png' -delete
$ find ROM/system/media/wallpaper/ -depth -type d -empty -delete
$ find -L . -type f -iname '*abc*' -exec bash -c 'mv "$0" "${0/abc/def}"' '{}' \;
$ export _pkg_name
$ find -H  -maxdepth 1 -type f \( -name '*.jpg' -o -name '*.png' \) -exec bash -c 'for img; do mv "$img" pics/"${_pkg_name}"; done' bash '{}' +
$ find . -type f -name '*.jpg' -exec bash -c 'for f; do mv $f ${f//"$0"}; done' $'\302\240' '{}' +
$ find . -type f -name '*.pgn' -print0 | xargs -0 -n4 -P4 mawk '/Result/ { split($0, a, "-"); res = substr(a[1], length(a[1]), 1); if (res == 1) white++; if (res == 0) black++; if (res == 2) draw++ } END { print white+black+draw, white, black, draw }' | mawk '{games += $1; white += $2; black += $3; draw += $4; } END { print games, white, black, draw }'
  1. Dont forget the quotes to prevent shell expansion.
  2. In the fourth example, the random string argument (i.e. bash) preceding {} is a must. It serves as the very first argument for -exec bash command. You can either quote it or not.
  3. Variables (i.e. _pkg_name) should be exported before calling 'find' as it spawns a new process.
  4. -print0 prints the full file name on the standard output, followed by a null character (instead of the newline character that -print uses). This allows file names that contain newlines or other types of white space to be correctly interpreted by programs that process the find output. This option corresponds to the -0 option of xargs.

Sometimes we want to search for directories instead of files. As the very first result is the root directory itself, we may want to omit the 'bash' arbitrary string such that the root directory becomes the $0.

~ $ find -H . -type d -name '*figs*' -exec bash -c 'for d; do rm -rf $d; done' '{}' +
~ $ find -H . -type d -name '*figs*' -exec bash -c 'for d; do rm -rf $d; done' bash '{}' +

The second version will remove current directory as well. Removing the 'bash' string will leaves it untouched.

Bulk rename

a=1
for i in *.jpg; do
  new=$(printf "%04d.jpg" "$a") #04 pad to length of 4
  mv -- "$i" "$new"
  let a=a+1
done

Number range

# -w means equal width
$ seq -w 5 20

netstat

$ netstat -npeatu
$ ss -npeatu

Try l instead of a.

net-tools is deprecated in favor of iproute2. The alternative to netstat is ss.

ln

Soft/symbolic link (symlink) points to file's directory entry while hard link points to file's inode. No matter of symlink or hard link, both are real but special files.

When delete a symlink file, the orginal file is not touched. However, to delete a hard link file, the original file's hard link references count will be decreased by 1. If the final count equal to 0, then the original file is deleted as well.

Encoding/charset conversion

Charset is a set of character entities while encoding is its representation in the terms of bytes and bits. In Enca, the word encoding means the same as representation of text, i.e. the relation between sequence of character entities constituting the text and sequence of bytes (bits) constituting the file.

   $ enca/file filename, detect encoding
   $ enconv filename, convert to *locale*
   $ enconv/enca -x gb2312 filename, convert to specified encoding
  1. enca/enconv is a tool superior to iconv.
  2. It does NOT make backup.

CR-LF/LF

  1. CR: Carriage Return, '\r', '\o015', '\xd'.
  2. LF: Line Feeding, '\n', '\o12', '\xa'.
  3. Some DOS files are ended with CR-LF, while others are LF-CR.

Detect line ending:

$ hexdump -c input.file, detect line ending
$ sed -n l input.file
$ file input.file
$ od -t c input.file

Conversion

app-text/dos2unix:

$ dos2unix/unix2dos -b input.file, '-b' makes a backup

perl:

$ perl -pi -e 's/\r//g' input.file
$ perl -pi -e 's/\n/\r\n/g' input.file

sed:

$ sed -i.bak 's/\r$//g' input.file
$ sed -i.bak 's/$/\r/g' input.file

tr:

$ tr -d '\r' < input.file > output.file, cannot do it the other way round

nano:

Type Ctrl-O and before confirming, type Alt-D (DOS) or Alt-M (Mac) to change the format.

Refer to remove CRs from CR-LF line terminators.

BOM

~ $ sed -i.bak '1s/^\xEF\xBB\xBF//' orig.txt

Remove whitespace

echo:

# `echo' without double quotes; squeeze sequencial spaces within the string to single one
str="   how   are	you   "
echo -e "$str"
echo -e $str
str=`echo $str`

sed:

$ str=" how    are    you   "
$ echo "$str" | sed 's/^[ \t]*//'
$ echo "$str" | sed 's/[ \t]*$//'
$ sed -e 's/[[:space:]]*$//' <<< "$str"
$ sed 's/^[ \t]*//;s/[ \t]*$//' <<< "$str"

awk/gawk:

# even sequencial tabs (maybe other blank characters) are squeezed to single space within string
$ awk '{$1=$1};1' file
$ awk '{$1=$1;print}' file

Empty line

~ $ sed '/^[[:space:]]*$/d' file.txt
~ $ sed '/^\s*$/d' file.txt
~ $ sed 's/^\s*$//g' file.txt   # failure as empty line replaced by a blank line

~ $ grep . file.txt

newline

Replace newline with another character:

~ $ tr '\n' ',' < file.txt

~ $ sed 's/\n/,/g' file.txt     # failure as sed read line but does not include the line terminator - newline
~ $ sed -z 's/\n/,/g' file.txt  # tell sed to use NULL as line terminator, so that newline is included when reading a line

read line by line

#!/bin/bash

input=~/workspace/bash/zh.txt
output=~/workspace/bash/zh.dict.yaml

> "$output"

while IFS=$'\t' read -r left right
do
    line=${left}$'\t'
    for word in $right; do
	if [[ $word == *"ā"* ]]; then
	    tmp=`sed 's/ā/a/' <<< $word`
	    line=$line${tmp}1' '
	elif [[ $word == *"á"* ]]; then
	    tmp=`sed 's/á/a/' <<< $word`
	    line=$line${tmp}2' '
	elif [[ $word == *"ǎ"* ]]; then
	    tmp=`sed 's/ǎ/a/' <<< $word`
	    line=$line${tmp}3' '
	elif [[ $word == *"à"* ]]; then
	    tmp=`sed 's/à/a/' <<< $word`
	    line=$line${tmp}4' '
	elif [[ $word == *"ē"* ]]; then
	    tmp=`sed 's/ē/e/' <<< $word`
	    line=$line${tmp}1' '
	elif [[ $word == *"é"* ]]; then
	    tmp=`sed 's/é/e/' <<< $word`
	    line=$line${tmp}2' '
	elif [[ $word == *"ě"* ]]; then
	    tmp=`sed 's/ě/e/' <<< $word`
	    line=$line${tmp}3' '
	elif [[ $word == *"è"* ]]; then
	    tmp=`sed 's/è/e/' <<< $word`
	    line=$line${tmp}4' '
	elif [[ $word == *"ō"* ]]; then
	    tmp=`sed 's/ō/o/' <<< $word`
	    line=$line${tmp}1' '
	elif [[ $word == *"ó"* ]]; then
	    tmp=`sed 's/ó/o/' <<< $word`
	    line=$line${tmp}2' '
	elif [[ $word == *"ǒ"* ]]; then
	    tmp=`sed 's/ǒ/o/' <<< $word`
	    line=$line${tmp}3' '
	elif [[ $word == *"ò"* ]]; then
	    tmp=`sed 's/ò/o/' <<< $word`
	    line=$line${tmp}4' '
	elif [[ $word == *"ī"* ]]; then
	    tmp=`sed 's/ī/i/' <<< $word`
	    line=$line${tmp}1' '
	elif [[ $word == *"í"* ]]; then
	    tmp=`sed 's/í/i/' <<< $word`
	    line=$line${tmp}2' '
	elif [[ $word == *"ǐ"* ]]; then
	    tmp=`sed 's/ǐ/i/' <<< $word`
	    line=$line${tmp}3' '
	elif [[ $word == *"ì"* ]]; then
	    tmp=`sed 's/ì/i/' <<< $word`
	    line=$line${tmp}4' '
	elif [[ $word == *"ū"* ]]; then
	    tmp=`sed 's/ū/u/' <<< $word`
	    line=$line${tmp}1' '
	elif [[ $word == *"ú"* ]]; then
	    tmp=`sed 's/ú/u/' <<< $word`
	    line=$line${tmp}2' '
	elif [[ $word == *"ǔ"* ]]; then
	    tmp=`sed 's/ǔ/u/' <<< $word`
	    line=$line${tmp}3' '
	elif [[ $word == *"ù"* ]]; then
	    tmp=`sed 's/ù/u/' <<< $word`
	    line=$line${tmp}4' '
	elif [[ $word == *"ǖ"* ]]; then
	    tmp=`sed 's/ǖ/ü/' <<< $word`
	    line=$line${tmp}1' '
	elif [[ $word == *"ǘ"* ]]; then
	    tmp=`sed 's/ǘ/ü/' <<< $word`
	    line=$line${tmp}2' '
	elif [[ $word == *"ǚ"* ]]; then
	    tmp=`sed 's/ǚ/ü/' <<< $word`
	    line=$line${tmp}3' '
	elif [[ $word == *"ǜ"* ]]; then
	    tmp=`sed 's/ǜ/ü/' <<< $word`
	    line=$line${tmp}4' '
	fi
    done
    sed -e 's/[[:space:]]*$//' <<< $line >/dev/null
    echo "$line" >> "$output"
done < "$input"
  1. IFS= option before read command prevents leading/trailing whitespace from being trimmed.
  2. The -r option passed to read command prevents backslash escapes from being interpreted.

Another example:

#!/bin/bash
file="/etc/passwd"
while IFS=: read -r f1 f2 f3 f4 f5 f6 f7
do
        # display fields using f1, f2,..,f7
        printf 'Username: %s, Shell: %s, Home Dir: %s\n' "$f1" "$f7" "$f6"
done <"$file"

Third version:

while read -p "input:" line
do
    echo "$line"
    printf "%s\n" "${line}"
done  < "${1:-/dev/stdin}"

Read from stdin or from a file parameter.

Here is an elegant version, just reading a line:

infd=0 ; [[ "${1}" ]] && : {infd}< "${1}"; read -p "input:" -r line <&"${infd}" # read -u $infd -r line

darkhttpd

$ ./darkhttpd ~/workspace/public_html --pidfile ~/workspace/httpd_traces/httpd.pid --daemon --maxconn 1 --no-listing --log ~/workspace/httpd_traces/access.log --forward example.com:8080 http://www.example.com:8080
or
$ ./darkhttpd ~/workspace/public_html --maxconn 1 --no-listing --log ~/workspace/httpd_traces/access.log --forward example.com:8080 http://www.example.com:8080 &

SSL cert

By openssl s_client:

~ $ openssl s_client -connect chat.freenode.net:6697 </dev/null
~ $ openssl s_client -connect chat.freenode.net:6697 </dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > freenode.pem
  1. -showcerts show all certificates in the chain, including the CA certificate, otherwise only the server certificate was downloaded.
  2. If the remote server is using SNI (that is, sharing multiple SSL hosts on a single IP address) you will also need to send the correct -servername in order to get the right certificate.

By gnutls-cli:

~ $ gnutls-cli --print-cert chat.freenode.net:6697 </dev/null
~ $ gnutls-cli --print-cert chat.freenode.net:6697 </dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > freenode.pem
~ $ gnutls-cli --print-cert --save-cert=freenode.pem chat.freenode.net:6697 </dev/null
  1. --sni-hostname serves the same role as -servername of openssl s_client.
  2. gnutls-cli always downloads the full certificate chain.

Examine the certificate downloaded:

~ $ cat freenode.pem; less freenode.pem
~ $ openssl x509 -noout -text -in freenode.pem
~ $ openssl x509 -noout -sha256 -fingerprint -in freenode.pem

curl/wget

Use curl/wget to test web/ftp service

~ $ wget -d http://www.example.com
~ $ wget -S http://www.example.com (Print the headers sent by HTTP servers and responses sent by FTP servers)
~ $ curl -v http://www.example.com
~ $ curl -I http://www.example.com (Fetch the headers only)
~ $ curl -i http://www.example.com (Include the HTTP response headers in the output)
~ $ curl --trace/--trace-ascii test.log http://www.example.com (Enables a full trace dump of all incoming and outgoing data, including descriptive information, to the given output file)
~ $ curl --socks5 127.0.0.1:1080 -v https://www.youtube.com (Test socks5 proxy)
~ $ curl --socks5-hostname 127.0.0.1:1080 -v https://www.youtube.com

Use curl to download a file

~ $ curl -OLv http://www.example.com/file.txt
~ $ curl -Lv -o new_file.txt http://www.example.com/file.txt

mtr/ping

~ $ mtr -rw 12.34.56.78

Image crop and keep aspect ratio

Filter the images:

#!/bin/bash

_input="kwdes.txt"

# Save as UTF-16 txt
# C-x RET r, revert-buffer-with-coding-system, UTF-8
sed -i.bak 's/\xC2\xA0//g' "${_input}" # non-breaking space
sed -i.bak 's/\r//g' "${_input}" # crlf to lf
sed -i.bak 's/[[:blank:]]*$//' "${_input}" # leading white spaces
sed -i.bak 's/^[[:blank:]]*//' "${_input}" # trailing white spaces
sed -i.bak '/^$/d' "${_input}" # empty lines
sed -i.bak -e '1d' "${_input}" # delete the 1st line
wc -l "${_input}"
#mkdir -p $(cut -d$'\t' -f1 "${_input}")
#awk -F'\t' 'FNR==linum { print $1, $4, $5 }' "${_input}"
#awk -F'\t' 'FNR==linum { printf("%s\t%s\t%s\n" $1, $4, $5) }' "${_input}"
#awk -F'\t' 'BEGIN { OFS=FS } FNR==linum { print $1, $2, $3 }' <<< $'a\tb\tc\td\n123\n'

Crop the images:

#!/bin/bash

# https://www.zedge.net/find/wallpapers/keyword, 8 * 1080x1920, com.uphone.wallpaperkeyword_description/keyword_date_num.jpg
# https://wallpaperscraft.com/catalog/keyword/1440x2560/page2
# https://wall.alphacoders.com/search.php?search=Minecraft#sorting_options
# icon_{1024*500,180*120}.jpg, ic_launcher_{512,192,148,96,72,48}.jpg
# magick -verbose input.jpg -trim -resize "1024x500^" -gravity center -extent "1024x500+0+0" +repage output.jpg # -extent -or -crop
# Parameters: line number > 1, selected filename

shopt -s extglob
#shopt -s nullglob

_workspace="${HOME}/WLshare"
cd "${_workspace}"

_input="kwdes.txt"

_linum="$1"
if [[ "${_linum}" < 1 ]]; then
    echo "line number starts from 1"
    exit
fi

_keyword=$(awk -F'\t' -v linum="${_linum}" 'FNR==linum {print $1}' "${_input}")
_keyword="${_keyword//[[:blank:]]/}"
echo "Keyword: ${_keyword}"

_pkg_name=$(awk -F'\t' -v linum="${_linum}" 'FNR==linum {print $4}' "${_input}")
echo "Package name: ${_pkg_name}"

rm -rf "${_keyword}"/*
mkdir -p "${_keyword}"/"${_pkg_name}"

_select="$2"; echo "Select: ${_select}"
_icon=(1024x500 180x120)
for i in "${_icon[@]}"; do
    magick "${_select}" -resize "${i}^" -gravity center -extent "${i}+0+0" +repage "${_keyword}"/"icon_${i%x*}.jpg"
done
_launcher=(512x512 192x192 148x148 96x96 72x72 48x48)
for i in "${_launcher[@]}"; do
    magick "${_select}" -resize "${i}^" -gravity center -extent "${i}+0+0" +repage "${_keyword}"/"ic_launcher_${i%x*}.jpg"
done

cp -f -- +(*.jpg|*.png) "${_keyword}"/"${_pkg_name}" 2>/dev/null
cd "${_keyword}"/"${_pkg_name}"
_size="1080x1920"
_counter=1
for f in *.+(jpg|png) ; do
    if [[ -f "$f" ]]; then
	magick "$f" -resize "${_size}^" -gravity center -extent "${_size}+0+0" +repage "$f"
	mv -f -- "$f" "${_keyword}_"$(date -I)"_${_counter}.jpg"
	_counter=$((_counter + 1))
    fi
done

# { cd ../../; rm -f -- *.+(jpg|png); }
echo 'done!'