sass, scss how to check a variable for whether it is just a number or px , rem , vw , % - sass

sass
for a mixin you need to check a variable
how to check a variable for whether it is just a number
for example
`
$a: 11
$b: 11 rem
$c: 11%
$d: 11em
=mixing($a, $b,$c,$d){
font-size: $a
font-weight:$c
padding: $d $b
}
+mixing($a, $b,$c,$d)
`
the check is needed in order to filter valid values
and if a prime number 11 is entered, then convert it to 11rem or 11%
$a: 11
$b: 11 rem
$c: 11%
$d: 11em
=mixing($a, $b,$c,$d){
font-size: $a
font-weight:$c
padding: $d $b
}
+mixing($a, $b,$c,$d)

Related

How to get the RGB values of a 256-color palette terminal color

Thanks to ANSI escape codes, when you run:
echo -e "\e[38;5;74mHi"
This will display "Hi", colored with the palette color 74 (commonly a blue/cyan, but it may be another color, depending on the defined palette):
But how can I retrieve the red/green/blue components of the given color (74 in this example) actually shown by the terminal emulator?
Expected result
Run this imaginary command in bash:
$ palette-to-rgb 74
Expected output:
#5fafd7 # Or any other parsable output like rgb(95,175,215)
What did I try
The only (ugly) way I found to get the RGB components is to make a screenshot and use some graphical program like gimp to retrieve the RGB values.
I think it's not possible to retrieve the color codes programmatically. A possible way would be a kind of ANSI sequence,
but it doesn't seem to exist.
So as a workaround, it is possible to get the standard values; I made this bash script for this aim (for int calculations, I got help from the source of st):
#!/bin/bash
usage() {
echo "palette-to-rgb [COL] [-rgb] [-raw] [-C] [-h]"
echo "Show RGB values of the standard terminal 256-color palette."
echo "If COL is given (number 0-255), show only the given color."
echo "Options:"
echo " -rgb Format as \"rgb(R, G, B)\" instead of \"#RRGGBB\""
echo " -raw Show the value only"
echo " -C Force coloring even when piped"
}
std_colors=(
0 0 0 # 0 black
128 0 0 # 1 red
0 128 0 # 2 green
128 128 0 # 3 yellow
0 0 128 # 4 blue
128 0 128 # 5 magenta
0 128 128 # 6 cyan
192 192 192 # 7 white (light grey)
128 128 128 # 8 grey
255 0 0 # 9 bright red
255 255 0 # 10 bright green
0 255 0 # 11 bright yellow
0 0 255 # 12 bright blue
255 0 255 # 13 bright magenta
0 255 255 # 14 bright cyan
255 255 255 # 15 bright white
)
# 6x6x6 cube color component
cube_component() {
local i=$(( (($1 - 16) / $2) % 6 ))
(( $i == 0 )) && echo 0 || echo $(( ( 14135 + 10280 * $i ) / 256 ))
}
get_color() {
local r g b fmt
if (( $1 < 16 )); then
r=${std_colors[$(( $1 * 3 + 0 ))]}
g=${std_colors[$(( $1 * 3 + 1 ))]}
b=${std_colors[$(( $1 * 3 + 2 ))]}
elif (( $1 < 232 )); then # < 6*6*6+16 ?
# colors 16-231 (6x6x6 cube):
r=$(cube_component $1 36)
g=$(cube_component $1 6)
b=$(cube_component $1 1)
else
# colors 232-255 (grayscale):
r=$(( ( 2056 + 2570 * ($1 - 232) ) / 256 ))
g=$r
b=$r
fi
[[ -n $rgb ]] && fmt='rgb(%i, %i, %i)' || fmt='#%02x%02x%02x'
printf "$fmt\n" $r $g $b
}
print_color() {
if [[ -n $raw ]]; then
get_color $1
else
# Show a colored box if not piped (or with option -C)
[[ -t 1 || -n $colored ]] && echo -en "\e[48;5;${1}m \e[0m "
printf '%03i: %s\n' $1 "$(get_color $1)"
fi
}
color= colored= rgb= raw=
for arg in "$#"; do
if [[ $arg == -h ]]; then usage; exit
elif [[ $arg =~ ^[0-9]+$ ]]; then
(( $arg > 255 )) && { echo "Wrong color code" >&2; exit 1; }
color=$arg
elif [[ $arg == -C ]]; then colored=1
elif [[ $arg == -rgb ]]; then rgb=1
elif [[ $arg == -raw ]]; then raw=1
else echo "Wrong arg: $arg" >&2; exit 1
fi
done
if [[ -n $color ]]; then
print_color $color
else
for n in {0..255}; do
print_color $n
done
fi
Output:
000: #000000
001: #800000
002: #008000
003: #808000
004: #000080
005: #800080
006: #008080
007: #c0c0c0
008: #808080
009: #ff0000
010: #ffff00
011: #00ff00
012: #0000ff
013: #ff00ff
014: #00ffff
015: #ffffff
016: #000000
017: #00005f
018: #000087
019: #0000af
020: #0000d7
021: #0000ff
022: #005f00
023: #005f5f
024: #005f87
025: #005faf
026: #005fd7
027: #005fff
028: #008700
029: #00875f
030: #008787
031: #0087af
032: #0087d7
033: #0087ff
034: #00af00
035: #00af5f
036: #00af87
037: #00afaf
038: #00afd7
039: #00afff
040: #00d700
041: #00d75f
042: #00d787
043: #00d7af
044: #00d7d7
045: #00d7ff
046: #00ff00
047: #00ff5f
048: #00ff87
049: #00ffaf
050: #00ffd7
051: #00ffff
052: #5f0000
053: #5f005f
054: #5f0087
055: #5f00af
056: #5f00d7
057: #5f00ff
058: #5f5f00
059: #5f5f5f
060: #5f5f87
061: #5f5faf
062: #5f5fd7
063: #5f5fff
064: #5f8700
065: #5f875f
066: #5f8787
067: #5f87af
068: #5f87d7
069: #5f87ff
070: #5faf00
071: #5faf5f
072: #5faf87
073: #5fafaf
074: #5fafd7
075: #5fafff
076: #5fd700
077: #5fd75f
078: #5fd787
079: #5fd7af
080: #5fd7d7
081: #5fd7ff
082: #5fff00
083: #5fff5f
084: #5fff87
085: #5fffaf
086: #5fffd7
087: #5fffff
088: #870000
089: #87005f
090: #870087
091: #8700af
092: #8700d7
093: #8700ff
094: #875f00
095: #875f5f
096: #875f87
097: #875faf
098: #875fd7
099: #875fff
100: #878700
101: #87875f
102: #878787
103: #8787af
104: #8787d7
105: #8787ff
106: #87af00
107: #87af5f
108: #87af87
109: #87afaf
110: #87afd7
111: #87afff
112: #87d700
113: #87d75f
114: #87d787
115: #87d7af
116: #87d7d7
117: #87d7ff
118: #87ff00
119: #87ff5f
120: #87ff87
121: #87ffaf
122: #87ffd7
123: #87ffff
124: #af0000
125: #af005f
126: #af0087
127: #af00af
128: #af00d7
129: #af00ff
130: #af5f00
131: #af5f5f
132: #af5f87
133: #af5faf
134: #af5fd7
135: #af5fff
136: #af8700
137: #af875f
138: #af8787
139: #af87af
140: #af87d7
141: #af87ff
142: #afaf00
143: #afaf5f
144: #afaf87
145: #afafaf
146: #afafd7
147: #afafff
148: #afd700
149: #afd75f
150: #afd787
151: #afd7af
152: #afd7d7
153: #afd7ff
154: #afff00
155: #afff5f
156: #afff87
157: #afffaf
158: #afffd7
159: #afffff
160: #d70000
161: #d7005f
162: #d70087
163: #d700af
164: #d700d7
165: #d700ff
166: #d75f00
167: #d75f5f
168: #d75f87
169: #d75faf
170: #d75fd7
171: #d75fff
172: #d78700
173: #d7875f
174: #d78787
175: #d787af
176: #d787d7
177: #d787ff
178: #d7af00
179: #d7af5f
180: #d7af87
181: #d7afaf
182: #d7afd7
183: #d7afff
184: #d7d700
185: #d7d75f
186: #d7d787
187: #d7d7af
188: #d7d7d7
189: #d7d7ff
190: #d7ff00
191: #d7ff5f
192: #d7ff87
193: #d7ffaf
194: #d7ffd7
195: #d7ffff
196: #ff0000
197: #ff005f
198: #ff0087
199: #ff00af
200: #ff00d7
201: #ff00ff
202: #ff5f00
203: #ff5f5f
204: #ff5f87
205: #ff5faf
206: #ff5fd7
207: #ff5fff
208: #ff8700
209: #ff875f
210: #ff8787
211: #ff87af
212: #ff87d7
213: #ff87ff
214: #ffaf00
215: #ffaf5f
216: #ffaf87
217: #ffafaf
218: #ffafd7
219: #ffafff
220: #ffd700
221: #ffd75f
222: #ffd787
223: #ffd7af
224: #ffd7d7
225: #ffd7ff
226: #ffff00
227: #ffff5f
228: #ffff87
229: #ffffaf
230: #ffffd7
231: #ffffff
232: #080808
233: #121212
234: #1c1c1c
235: #262626
236: #303030
237: #3a3a3a
238: #444444
239: #4e4e4e
240: #585858
241: #626262
242: #6c6c6c
243: #767676
244: #808080
245: #8a8a8a
246: #949494
247: #9e9e9e
248: #a8a8a8
249: #b2b2b2
250: #bcbcbc
251: #c6c6c6
252: #d0d0d0
253: #dadada
254: #e4e4e4
255: #eeeeee
A related question: How to generate 256 color palette for Linux terminal in HTML/jquery
Try this for hex codes:
xdef="$HOME/.Xresources"
colors=( $( sed -re '/^!/d; /^$/d; /^#/d; s/(\*color)([0-9]):/\10\2:/g;' $xdef | grep 'color[01][0-9]:' | sort | sed 's/^.*: *//g' )
)
echo
for i in {0..7}; do echo -en "\e[$((30+$i))m ${colors[i]} \u2588\u2588 \e[0m"; done
echo
for i in {8..15}; do echo -en "\e[1;$((22+$i))m ${colors[i]} \u2588\u2588 \e[0m"; done
echo -e "\n"
All credits to https://github.com/stark/color-scripts/blob/master/color-scripts/hex-block .
Note that this script won't work on macOS.
And this for color with escape codes:
T='•••' # The text for the color test
echo -e "\n def 40m 41m 42m 43m 44m 45m 46m 47m";
for FGs in ' m' ' 1m' ' 30m' '1;30m' ' 31m' '1;31m' ' 32m' \
'1;32m' ' 33m' '1;33m' ' 34m' '1;34m' ' 35m' '1;35m' \
' 36m' '1;36m' ' 37m' '1;37m';
do FG=${FGs// /}
echo -en " $FGs \033[$FG $T "
for BG in 40m 41m 42m 43m 44m 45m 46m 47m;
do echo -en "$EINS \033[$FG\033[$BG $T \033[0m";
done
echo;
done
echo
Source: https://github.com/stark/color-scripts/blob/master/color-scripts/colortest .

Split very large files on record border

I have to split a very large file into N smaller files with the following constraints:
I have to split on record border
Record separator can be any character
the number of records in the resulting N files should be the same (+/- 1 record)
I can just use bash and standard coreutils (I have a working solution in Perl but we're not allowed to install Perl/Python/etc)
This is not a real constraint but - if possible - I'd like to scan the original (large) file just once.
Sort order of the resulting files is not important.
My working solution in Perl reads the original file and writes...
- the 1st record to the first file
- ...
- the Nth record to the Nth file
- the N+1 record back to the first file
- etc
So - at the end - with a single scan of the initial file I do get several smaller files with the same number of records (+/- 1).
For example, assume this is the input file:
1,1,1,1A2,2,2,2A3,
3,3,3A4,4,4,4A5,5,
5,5A6,6,6,6A7,7,7,
7,A8,8,8,8A9,9,9,9
A0,0,0,0
With record separator = 'A' and N = 3 I should get three files:
# First file:
1,1,1,1A2,2,2,2A3,
3,3,3
# Second file
4,4,4,4A5,5,
5,5A6,6,6,6
# Third file:
7,7,7,
7,A8,8,8,8A9,9,9,9
A0,0,0,0
UPDATE
Here you have the perl code. I tried to make it as simple and readable as I can:
#!/usr/bin/perl
use warnings;
use strict;
use locale;
use Getopt::Std;
#-----------------------------------------------------------------------------
# Declaring variables
#-----------------------------------------------------------------------------
my %op = (); # Command line parameters hash
my $line = 0; # Output file line number
my $fnum = 0; # Output file number
my #fout = (); # Output file names array
my #fhnd = (); # Output file handles array
my #ifiles = (); # Input file names
my $i = 0; # Loop variable
#-----------------------------------------------------------------------------
# Handling command line arguments
#-----------------------------------------------------------------------------
getopts("o:n:hvr:", \%op);
die "Usage: lfsplit [-h] -n number_of_files",
" [-o outfile_prefix] [-r rec_sep_decimal] [-v] input_file(s)\n"
if $op{h} ;
if ( #ARGV ) {
#ifiles = #ARGV ;
} else {
die "No input files...\n" ;
}
$/ = chr($op{r}) if $op{r} ;
#-----------------------------------------------------------------------------
# Setting Default values
#-----------------------------------------------------------------------------
$op{o} |= 'out_' ;
#-----------------------------------------------------------------------------
# Body - split in round-robin to $op{n} files
#-----------------------------------------------------------------------------
for ( $i = 0 ; $i < $op{n} ; $i++ ) {
local *OUT ; # Localize file glob
$fout[$i] = sprintf "%s_%04d.out", $op{o}, $i ;
open ( OUT, "> $fout[$i]" ) or
die "[lfsplit] Error writing to $fout[$i]: $!\n";
push ( #fhnd , *OUT ) ;
}
$i = 0 ;
foreach ( #ifiles ) {
print "Now reading $_ ..." if $op{v} ;
open ( IN, "< $_" ) or
die "[lfsplit] Error reading $op{i}: $!\n" ;
while ( <IN> ) {
print { $fhnd[$i] } $_ ;
$i = 0 if ++$i >= $op{n} ;
}
close IN ;
}
for ( $i = 0 ; $i < $op{n} ; $i++ ) {
close $fhnd[$i] ;
}
#-----------------------------------------------------------------------------
# Exit
#-----------------------------------------------------------------------------
exit 0 ;
Just for kicks, a pure bash solution, no external programs and no forking (I think):
#!/bin/bash
input=$1
separator=$2
outputs=$3
i=0
while read -r -d"$separator" record; do
out=$((i % outputs)).txt
if ((i < outputs)); then
: > $out
else
echo -n "$separator" >> $out
fi
echo -n "$record" >> $out
((i++))
done < $input
Sadly this will reopen every file for every output operation. I'm sure it's possible to fix this, using <> to open a file descriptor and keep it open, but using that with non-literal file descriptors is a bit of a pain.

batch menu outlines and design [duplicate]

This question already has answers here:
Using box-drawing Unicode characters in batch files
(3 answers)
Closed 3 years ago.
a while ago i were googling to find out how I could make a batch menu with a more "professional look" instead of using symbols such as:
|=====|
|-----|
|_____|
to make outlines around a menu in batch.
but I had no luck.
today i randomly found this article:
https://web.archive.org/web/20151204182221/https://http-server.carleton.ca/~dmcfet/menu.html
and it explains that by using ms-dos (edit.com) I can do this.
but as my computer is a 64 bit win 10. I dot have the edit.com so.... how could I make this kind of menu look by hand? (printing special characters shown on left side of the header "STEP 3, Lines, Lines, Lines.")
Here's a batch + PowerShell menu maker I've been working on. Set the values in the batch portion, and the PowerShell stuff will auto resize and reposition as needed. When a selection is made, the console buffer restores its former contents, effectively vanishing the menu.
It looks like this:
Here's the code. Save it with a .bat extension.
<# : Batch portion
#echo off & setlocal enabledelayedexpansion
set "menu[0]=Format C:"
set "menu[1]=Send spam to boss"
set "menu[2]=Truncate database *"
set "menu[3]=Randomize user password"
set "menu[4]=Download Dilbert"
set "menu[5]=Hack local AD"
set "default=0"
powershell -noprofile "iex (gc \"%~f0\" | out-string)"
echo You chose !menu[%ERRORLEVEL%]!.
goto :EOF
: end batch / begin PowerShell hybrid chimera #>
$menutitle = "=== MENU ==="
$menuprompt = "Use the arrow keys. Hit Enter to select."
$maxlen = $menuprompt.length + 6
$menu = gci env: | ?{ $_.Name -match "^menu\[\d+\]$" } | %{
$_.Value.trim()
$len = $_.Value.trim().Length + 6
if ($len -gt $maxlen) { $maxlen = $len }
}
[int]$selection = $env:default
$h = $Host.UI.RawUI.WindowSize.Height
$w = $Host.UI.RawUI.WindowSize.Width
$xpos = [math]::floor(($w - ($maxlen + 5)) / 2)
$ypos = [math]::floor(($h - ($menu.Length + 4)) / 3)
$offY = [console]::WindowTop;
$rect = New-Object Management.Automation.Host.Rectangle `
0,$offY,($w - 1),($offY+$ypos+$menu.length+4)
$buffer = $Host.UI.RawUI.GetBufferContents($rect)
function destroy {
$coords = New-Object Management.Automation.Host.Coordinates 0,$offY
$Host.UI.RawUI.SetBufferContents($coords,$buffer)
}
function getKey {
while (-not ((37..40 + 13 + 48..(47 + $menu.length)) -contains $x)) {
$x = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown').VirtualKeyCode
}
$x
}
# http://goo.gl/IAmdR6
function WriteTo-Pos ([string]$str, [int]$x = 0, [int]$y = 0,
[string]$bgc = [console]::BackgroundColor, [string]$fgc = [Console]::ForegroundColor) {
if($x -ge 0 -and $y -ge 0 -and $x -le [Console]::WindowWidth -and
$y -le [Console]::WindowHeight) {
$saveY = [console]::CursorTop
$offY = [console]::WindowTop
[console]::setcursorposition($x,$offY+$y)
Write-Host $str -b $bgc -f $fgc -nonewline
[console]::setcursorposition(0,$saveY)
}
}
function center([string]$what) {
$what = " $what "
$lpad = " " * [math]::max([math]::floor(($maxlen - $what.length) / 2), 0)
$rpad = " " * [math]::max(($maxlen - $what.length - $lpad.length), 0)
WriteTo-Pos "$lpad $what $rpad" $xpos $line blue yellow
}
function menu {
$line = $ypos
center $menutitle
$line++
center " "
$line++
for ($i=0; $item = $menu[$i]; $i++) {
# write-host $xpad -nonewline
$rtpad = " " * ($maxlen - $item.length)
if ($i -eq $selection) {
WriteTo-Pos " > $item <$rtpad" $xpos ($line++) yellow blue
} else {
WriteTo-Pos " $i`: $item $rtpad" $xpos ($line++) blue yellow
}
}
center " "
$line++
center $menuprompt
1
}
while (menu) {
[int]$key = getKey
switch ($key) {
37 {} # left or up
38 { if ($selection) { $selection-- }; break }
39 {} # right or down
40 { if ($selection -lt ($menu.length - 1)) { $selection++ }; break }
# number or enter
default { if ($key -gt 13) {$selection = $key - 48}; destroy; exit($selection) }
}
}
FIX:
in order to create a menu like this. you will need to use an editor such as notepad++
open a new notepad ++ file. then goto format and select western europe OEM-US charset.
then simply go here: https://en.wikipedia.org/wiki/Box-drawing_character
and copy paste the symbols you want for your menu in notepad++ and save.
example:
Displayed in notepad++ :
echo ╔════════════════════════════╗
echo ║Bill register/database Menu ║
echo ╠════════════════════════════╣
echo ║ 1. -Register a bill ║
echo ║ 2. -Check Bill Info ║
echo ║ 3. Set payment ║
echo ╚════════════════════════════╝
displayed in normal notepad :
echo ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ»
echo ºBill register/database Menu º
echo ÌÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ͹
echo º 1. -Register a bill º
echo º 2. -Check Bill Info º
echo º 3. Set payment º
echo ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍͼ
so as you can see. it works with normal notepad too. but its easier to work with it in notepad++ as it displays what it actually look like.
It's not necessary to worry about code pages or file encodings at all.
When you are on windows 10 you can use the Character Set - DEC Line Drawing Mode
This uses ansi-escape sequences to show borders.
The border characters are represented by the characters jklmnqtuvwx.
for /F "delims=#" %%E in ('"prompt #$E# & for %%E in (1) do rem"') do set "\e=%%E"
REM *** Enable DEC Line Drawing Mode
echo %\e%(0
echo lqqk
echo x x
echo x x
echo mqqnqqk
echo x x
echo mqqj
REM *** Enable ASCII Mode (Default)
echo %\e%(B
The output is
┌──┐
│ │
│ │
└──┼──┐
│ │
└──┘
for Notepad with ASCII and CHCP 1250 / CHCP 1252 characters are:
É Í » ş Č Ľ

Concat folder/subfolder file of type X contents to variable

Im trying to write a script that:
Reads a single file to a variable
Iterates through all files in all subfolders of a type and adds their contents to that same variable
I had tried this path.
common_css=$(cat "$path/normalize.css") #$path is valid
find $path/common -type f -name *.css | while read f; do
common_css=$common_css $(cat "$f")
echo "$common_css"
done
echo "$common_css"
EX:
FILES
/normalize.css = body{ background-color : red; }
/common/name.css = table{ display : block; }
/common/oauth.conf = key="somekey"
/common/place.css = p{ font-size: 2em; }
DESIRED OUTPUT
body{ background-color : red; }
table{ display : block; }
p{ font-size: 2em; }
Try the following (assuming you really want to build up the entire string in memory):
common_css=$(cat "$path/normalize.css";
find "$path/common" -type f -name '*.css' -exec cat {} +)
A single command substitution can output the concatenation of the contents of all files of interest, no need for loops.
-exec cat {} +, thanks to the trailing +, passes as many matching file paths as will fit onto a single command line to cat, resulting in typically just one invocation.
I've added quoting to make the command more robust - notably, you should always quote the glob argument passed to -name, to prevent premature expansion by the shell.

How to validate a IPv6 address format with shell?

In my shell, I need check if a string is a valid IPv6 address.
I find two ways, neither of them is ideal enough to me.
One is http://twobit.us/2011/07/validating-ip-addresses/, while I wonder if it must be such complex for such a common requirement.
The other is expand ipv6 address in shell script, this is simple, but for major distribution of Linux, sipcalc isn't a common default utility.
So my question, is there a simple way or utility to validate a IPv6 address with shell?
Thanks in advance.
The code in the first link isn't particularly elegant, but modulo stylistic fixes, I don't think you can simplify much beyond that (and as indicated in a comment, it may already be too simple). The spec is complex and mandates a number of optional features, which is nice for the end user, but cumbersome for the implementor.
You could probably find a library for a common scripting language which properly encapsulates this logic in a library. My thoughts would go to Python, where indeed Python 3.3 includes a standard module called ipaddress; for older versions, try something like
#!/usr/bin/env python
import socket
import sys
try:
socket.inet_pton(socket.AF_INET6, sys.argv[1])
result=0
except socket.error:
result=1
sys.exit(result)
See also Checking for IP addresses
Here is a solution in POSIX compatible shell script that handles IPv4 and IPv6 addresses with an optional subnet mask. To test an IP that should not have a subnet mask just pass it a dummy one when performing the test. It seems like a lot of code but it should be significantly faster than using external programs like grep or scripts that are likely to fork.
Single IPv6 zero groups compressed to :: will be treated as invalid. The use of such representation is strongly discouraged, but technically correct. There is a note in the code explaining how to alter this behaivour if you wish to allow such addresses.
#!/bin/sh
set -e
# return nonzero unless $1 contains only digits, leading zeroes not allowed
is_numeric() {
case "$1" in
"" | *[![:digit:]]* | 0[[:digit:]]* ) return 1;;
esac
}
# return nonzero unless $1 contains only hexadecimal digits
is_hex() {
case "$1" in
"" | *[![:xdigit:]]* ) return 1;;
esac
}
# return nonzero unless $1 is a valid IPv4 address with optional trailing subnet mask in the format /<bits>
is_ip4() {
# fail if $1 is not set, move it into a variable so we can mangle it
[ -n "$1" ] || return
IP4_ADDR="$1"
# handle subnet mask for any address containing a /
case "$IP4_ADDR" in
*"/"* ) # set $IP4_GROUP to the number of bits (the characters after the last /)
IP4_GROUP="${IP4_ADDR##*"/"}"
# return failure unless $IP4_GROUP is a positive integer less than or equal to 32
is_numeric "$IP4_GROUP" && [ "$IP4_GROUP" -le 32 ] || return
# remove the subnet mask from the address
IP4_ADDR="${IP4_ADDR%"/$IP4_GROUP"}";;
esac
# backup current $IFS, set $IFS to . as that's what separates digit groups (octets)
IP4_IFS="$IFS"; IFS="."
# initialize count
IP4_COUNT=0
# loop over digit groups
for IP4_GROUP in $IP4_ADDR ;do
# return failure if group is not numeric or if it is greater than 255
! is_numeric "$IP4_GROUP" || [ "$IP4_GROUP" -gt 255 ] && IFS="$IP4_IFS" && return 1
# increment count
IP4_COUNT=$(( IP4_COUNT + 1 ))
# the following line will prevent the loop continuing to run for invalid addresses with many occurrences of .
# this makes no difference to the result, but may improve performance when validating many such invalid strings
[ "$IP4_COUNT" -le 4 ] || break
done
# restore $IFS
IFS="$IP4_IFS"
# return success if there are 4 digit groups, otherwise return failure
[ "$IP4_COUNT" -eq 4 ]
}
# return nonzero unless $1 is a valid IPv6 address with optional trailing subnet mask in the format /<bits>
is_ip6() {
# fail if $1 is not set, move it into a variable so we can mangle it
[ -n "$1" ] || return
IP6_ADDR="$1"
# handle subnet mask for any address containing a /
case "$IP6_ADDR" in
*"/"* ) # set $IP6_GROUP to the number of bits (the characters after the last /)
IP6_GROUP="${IP6_ADDR##*"/"}"
# return failure unless $IP6_GROUP is a positive integer less than or equal to 128
is_numeric "$IP6_GROUP" && [ "$IP6_GROUP" -le 128 ] || return
# remove the subnet mask from the address
IP6_ADDR="${IP6_ADDR%"/$IP6_GROUP"}";;
esac
# perform some preliminary tests and check for the presence of ::
case "$IP6_ADDR" in
# failure cases
# *"::"*"::"* matches multiple occurrences of ::
# *":::"* matches three or more consecutive occurrences of :
# *[^:]":" matches trailing single :
# *"."*":"* matches : after .
*"::"*"::"* | *":::"* | *[^:]":" | *"."*":"* ) return 1;;
*"::"* ) # set flag $IP6_EXPANDED to true, to allow for a variable number of digit groups
IP6_EXPANDED=0
# because :: should not be used for remove a single zero group we start the group count at 1 when :: exists
# NOTE This is a strict interpretation of the standard, applications should not generate such IP addresses but (I think)
# they are in fact technically valid. To allow addresses with single zero groups replaced by :: set $IP6_COUNT to
# zero after this case statement instead
IP6_COUNT=1;;
* ) # set flag $IP6_EXPANDED to false, to forbid a variable number of digit groups
IP6_EXPANDED=""
# initialize count
IP6_COUNT=0;;
esac
# backup current $IFS, set $IFS to : to delimit digit groups
IP6_IFS="$IFS"; IFS=":"
# loop over digit groups
for IP6_GROUP in $IP6_ADDR ;do
# if this is an empty group then increment count and process next group
[ -z "$IP6_GROUP" ] && IP6_COUNT=$(( IP6_COUNT + 1 )) && continue
# handle dotted quad notation groups
case "$IP6_GROUP" in
*"."* ) # return failure if group is not a valid IPv4 address
# NOTE a subnet mask is added to the group to ensure we are matching addresses only, not ranges
! is_ip4 "$IP6_GROUP/1" && IFS="$IP6_IFS" && return 1
# a dotted quad refers to 32 bits, the same as two 16 bit digit groups, so we increment the count by 2
IP6_COUNT=$(( IP6_COUNT + 2 ))
# we can stop processing groups now as we can be certain this is the last group, : after . was caught as a failure case earlier
break;;
esac
# if there are more than 4 characters or any character is not a hex digit then return failure
[ "${#IP6_GROUP}" -gt 4 ] || ! is_hex "$IP6_GROUP" && IFS="$IP6_IFS" && return 1
# increment count
IP6_COUNT=$(( IP6_COUNT + 1 ))
# the following line will prevent the loop continuing to run for invalid addresses with many occurrences of a single :
# this makes no difference to the result, but may improve performance when validating many such invalid strings
[ "$IP6_COUNT" -le 8 ] || break
done
# restore $IFS
IFS="$IP6_IFS"
# if this address contained a :: and it has less than or equal to 8 groups then return success
[ "$IP6_EXPANDED" = "0" ] && [ "$IP6_COUNT" -le 8 ] && return
# if this address contained exactly 8 groups then return success, otherwise return failure
[ "$IP6_COUNT" -eq 8 ]
}
Here are some tests.
# tests
TEST_PASSES=0
TEST_FAILURES=0
for TEST_IP in 0.0.0.0 255.255.255.255 1.2.3.4/1 1.2.3.4/32 12.12.12.12 123.123.123.123 101.201.201.109 ;do
! is_ip4 "$TEST_IP" && printf "IP4 test failed, test case '%s' returned invalid\n" "$TEST_IP" && TEST_FAILURES=$(( TEST_FAILURES + 1 )) || TEST_PASSES=$(( TEST_PASSES + 1 ))
done
for TEST_IP in ::1 ::1/128 ::1/0 ::1234 ::bad ::12 1:2:3:4:5:6:7:8 1234:5678:90ab:cdef:1234:5678:90ab:cdef \
1234:5678:90ab:cdef:1234:5678:90ab:cdef/127 1234:5678:90ab::5678:90ab:cdef/64 f:1234:c:ba:240::1 \
1:2:3:4:5:6:1.2.3.4 ::1.2.3.4 ::1.2.3.4/0 ::ffff:1.2.3.4 ;do
! is_ip6 "$TEST_IP" && printf "IP6 test failed, test case '%s' returned invalid\n" "$TEST_IP" && TEST_FAILURES=$(( TEST_FAILURES + 1 )) || TEST_PASSES=$(( TEST_PASSES + 1 ))
done
for TEST_IP in junk . / 0 -1.0.0.0 1.2.c.0 a.0.0.0 " 1.2.3.4" "1.2.3.4 " " " 01.0.0.0 09.0.0.0 0.0.0.01 \
0.0.0.09 0.09.0.0.0 0.01.0.0 0.0.01.0 0.0.0.a 0.0.0 .0.0.0.0 256.0.0.0 0.0.0.256 "" 0 1 12 \
123 1.2.3.4/s 1.2.3.4/33 1.2.3.4/1/1 ;do
is_ip4 "$TEST_IP" && printf "IP4 test failed, test case '%s' returned valid\n" "$TEST_IP" && TEST_FAILURES=$(( TEST_FAILURES + 1 )) || TEST_PASSES=$(( TEST_PASSES + 1 ))
done
for TEST_IP in junk "" : / :1 ::1/ ::1/1/1 :::1 ::1/129 ::12345 ::bog ::1234:345.234.0.0 ::sdf.d ::1g2 \
1:2:3:44444:5:6:7:8 1:2:3:4:5:6:7 1:2:3:4:5:6:7:8/1c1 1234:5678:90ab:cdef:1234:5678:90ab:cdef:1234/64 \
1234:5678:90ab:cdef:1234:5678::cdef/64 ::1.2.3.4:1 1.2.3.4:: ::1.2.3.4j ::1.2.3.4/ ::1.2.3.4:junk ::1.2.3.4.junk ;do
is_ip6 "$TEST_IP" && printf "IP6 test failed, test case '%s' returned valid\n" "$TEST_IP" && TEST_FAILURES=$(( TEST_FAILURES + 1 )) || TEST_PASSES=$(( TEST_PASSES + 1 ))
done
printf "test complete, %s passes and %s failures\n" "$TEST_PASSES" "$TEST_FAILURES"
Most distros come with package iproute2 (name may vary) preinstalled. So you can rely on the command ip for querying the routing table:
ip -6 route get <probe_addr>/128 >/dev/null 2>&1
Even on a machine without appropriate route this delivers rc=0 when the probe is in valid v6-syntax.
valid_ip(){
ip -6 route get "$1"/128 >/dev/null 2>&1
case "$?" in
0|2) return 0
1) return 1
esac
}
i took sgundlach's answer, but needed it on a machine which did not have ip6 connectivity so through testing and reading the manpage I found that I can trust exit code 1 to mean the syntax is invalid, meanwhile 0 is success and 2 is valid syntax but kernel error reported.
from the man page:
Exit status is 0 if command was successful, and 1 if there is a syntax error. If an error was reported by the kernel exit status is 2.

Resources