1

I want to print all environment variables on Linux using standard tools and bash. I need to see environment name and masked variable with the last 3 characters visible if variable length is greater than 5 (qwerty -> ***rty), and masked value with 1 visible character if it's less than 5 (or equal to 5) (zxc -> **c).

Example:

FOO=qwerty
BAR=asdfghjkl
BAZ=zxc

I'm expecting this command to print:

FOO=***rty
BAR=******jkl
BAZ=**c

The length of original value and masked is the same here.

At the moment I tried to implement this using this awk command, but it's handles only some of these cases by replacing all word with 3 * and cut the value to the last 3 characters (I'm not really good with awk, so maybe it could be implemented better):

env | awk -F= '{ if(length($2)>5) {printf("%s=***%s\n", $1, substr($2, length($2)-2))} }'
Kirill
  • 7,580
  • 6
  • 44
  • 95
  • This seems like you try to hide sensitive information (eg passwords, credit card numbers, ...). If this is the case, have a look at the following pages: [(SE) Is it secure to store passwords as environment variables?](https://stackoverflow.com/q/12461484/8344060), [(U&L)Hiding Password in Shell Scripts](https://unix.stackexchange.com/q/212329/273492), [(IS) How to securely store credentials in a bash variables?](https://security.stackexchange.com/q/150580) – kvantour Apr 05 '23 at 07:48
  • @kvantour thanks, in my case I'm trying to just debug container deployment in development environment, where no really sensitive information could be printed, but I want to hide even these params – Kirill Apr 05 '23 at 08:01
  • 1
    `perl -pe 's/(?<==)(.*)(.{3})$/ sprintf("%s%s", "*" x length($1), $2)/e'` – tripleee Apr 05 '23 at 08:02
  • See also https://stackoverflow.com/questions/11343665/unix-script-for-masking-sensitive-data-in-the-log-files – tripleee Apr 05 '23 at 08:52

5 Answers5

1

Try this script with awk gensub:

#!/bin/bash

env | awk -F= '{
  name=$1;
  value=$2;

  if (length(value) > 5) {
    mask=gensub(/./,"*","g",substr(value,1,length(value)-3));
    value=mask substr(value,length(value)-2);
  } else {
    mask=gensub(/./,"*","g",substr(value,1,length(value)-1));
    value=mask substr(value,length(value));
  }

  print name "=" value;
}'
protob
  • 3,317
  • 1
  • 8
  • 19
1

A solution in pure bash might be:

#!/bin/bash

env |
while IFS='=' read -r name value; do
    n=$((${#value} > 5 ? -3 : ${#value} > 0 ? -1 : 0))
    head=${value:0:n}
    printf '%s=%s%s\n' "$name" "${head//?/'*'}" "${value:n}"
done

Note: In shell programming, reading lines by a while read loop is inefficient. I'm assuming the output issued by the env command is small enough that this inefficiency isn't significant. You shouldn't use this technique for large inputs.

M. Nejat Aydin
  • 9,597
  • 1
  • 7
  • 17
  • You probably shouldn't, though - see [`while read` loop extremely slow compared to `cat`, why?](https://stackoverflow.com/questions/13762625/bash-while-read-loop-extremely-slow-compared-to-cat-why) – tripleee Apr 05 '23 at 08:48
1

If you want to be lazy and don't care about the correct amount of asterisks, you could just do:

$ env | sed 's/=\(...*\)\(...\)/=***\2/'

If the asterisks have to be the correct total, you could use awk. However, using the equal sign as a field delimiter is not the way forward as a single line might contain more than one equal sign:

$ env | awk '{i=index($0,"="); s=substr($0,i+1);l=length(s)}(l>5){x=substr(s,1,l-3); gsub(/./,"*",x); print substr($0,1,i) s1 substr(s,l-2); next}1'
kvantour
  • 25,269
  • 4
  • 47
  • 72
1

Here is a slightly more compact awk version that handles environment strings greater than 5-characters replacing all but the last three characters in the value with '*'. In the case of the string being 5-characters or less, all but the last character is replaced. That matches your example output, though you have not provided examples with 4 or 5 characters exactly.

Basically the awk script builds a mask of the correct number of "***" characters and a dynamic regular expression re of "..." with the number of replacement characters and calls sub() to make the replacement, e.g,

awk -F= '
{
  l = length($2)
  mask = ""
  re = ""
  for (i=0;i<(l>5?l-3:l-1);i++) {
    mask = mask "*"
    re = re "."
  }
  mask = "=" mask
  re = "=" re
  sub (re, mask)
  print
}' file

Example Use/Output

$ awk -F= '
> {
>   l = length($2)
>   mask = ""
>   re = ""
>   for (i=0;i<(l>5?l-3:l-1);i++) {
>     mask = mask "*"
>     re = re "."
>   }
>   mask = "=" mask
>   re = "=" re
>   sub (re, mask)
>   print
> }' << eof
> FOO=qwerty
> BAR=asdfghjkl
> BAZ=zxc
> eof
FOO=***rty
BAR=******jkl
BAZ=**c
David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
0

This approach pre-makes a string of "*" x 2,400, and replaces via substr()-ing instead of regex, so unless you have data values beyond 2,400 bytes/chars, this should suffice.

mawk 'BEGIN { FS = OFS = "="; _=(_=(_="*")_ _)_
              gsub(__,_,_) gsub(__,_,_); _ +=(_^= ___ = _)+_ } 
$NF = sprintf("%.*s%s",__ -=_^((_+_) <= (__ = length($NF))), ___, substr($NF, ++__))' 
FOO=***rty
BAR=******jkl
BAZ=**c
RARE Kpop Manifesto
  • 2,453
  • 3
  • 11