THC
THC

Reputation: 232

Bash detect characters that don't have corresponding var

I have done a script which define a variable for each letters (for an encryption process), but I want spaces to be in the ouput.

I have this :

eiput_splitted="$(echo $einput | sed -e 's/\(.\)/\1\n/g')"
export eouput=""

for input in $eiput_splitted; do
    export eouput=$eouput${!input}
    //added code here
done

but I don't know how to detect spaces in the loop. I tried

if [ $input = \  ]
if [ $input = *\ * ]
if [ $input = *"\ "* ]
if [ $input = "*\ *" ]
if [ -z ${!input+x} ] (to detect unset variables, but doesn't seem to work..)

Can you help me please ?

Upvotes: 2

Views: 164

Answers (5)

gniourf_gniourf
gniourf_gniourf

Reputation: 46853

Your design is not very good. You should not use plain variables like so! You'll run into problems if you want to encode special characters like _, *, é, etc.

Instead, you should use associative arrays. Here's a full pure Bash working example:

#!/bin/bash

# read message to encode on stdin and outputs message to stdout

declare -A lookup

lookup=(
   [a]=n [b]=o [c]=p [d]=q [e]=r [f]=s [g]=t [h]=u [i]=v [j]=w [k]=x
   [l]=y [m]=z [n]=a [o]=b [p]=c [q]=d [r]=e [s]=f [t]=g [u]=h [v]=i
   [w]=j [x]=k [y]=l [z]=m
)

while IFS= read -r -d '' -n 1 char; do
   if [[ -z $char ]]; then
      # null byte
      printf '\0'
   elif [[ ${lookup["$char"]} ]]; then
      # defined character
      printf '%s' "${lookup["$char"]}"
   elif [[ $char = @(' '|$'\n') ]]; then
      # space and newline
      printf '%s' "$char"
   else
      # other characters passed verbatim with warning message
      printf >&2 "Warning, character \`%s' not supported\n" "$char"
      printf '%s' "$char"
   fi
done

I called this script banana, chmod +x banana and:

$ ./banana <<< "hello stackoverflow"
uryyb fgnpxbiresybj
$ ./banana <<< "uryyb fgnpxbiresybj"
hello stackoverflow

If you want to encode the content of the variable input and store the encoded text in the variable output, just modify the main loop as so:

output=
linput=$input
while [[ $linput ]]; do
   char=${linput::1}
   linput=${linput:1}
   if [[ ${lookup["$char"]} ]]; then
      # defined character
      output+=${lookup["$char"]}
   elif [[ $char = @(' '|$'\n') ]]; then
      # space and newline
      output+=$char
   else
      # other characters passed verbatim with warning message
      printf >&2 "Warning, character \`%s' not supported\n" "$char"
      output+=$char
   fi
done

In this case, I omitted the check for the null byte, as a Bash variable can't contain the null byte.


Proceeding like so, you can really encode any character you like (even null bytes—in the first version—and newline characters).


Edit (re your comment).

You want to use this for a Caesar cipher script. Your concern is that you prompt the user for the number used for the shift and you don't know how to use this method in your case. The key is that it's rather easy to generate the lookup table.

Here's a fully working example:

#!/bin/bash

chrv() {
   local c
   printf -v c '\\%03o' "$1"
   printf -v "$2" "$c"
}

ansi_normal=$'\E[0m'
ansi_lightgreen=$'\E[02m'

while true; do
   printf '%s' "Encryption key (number from ${ansi_lightgreen}1$ansi_normal to ${ansi_lightgreen}26$ansi_normal): "
   read -re ekey
   if [[ $ekey = +([[:digit:]]) ]]; then
      ((ekey=10#$ekey))
      ((ekey>=1 && ekey<=26)) && break
   fi
   printf 'Bad number. Try again.\n'
done

# Build the lookup table according to this number
declare -A lookup

for i in {0..25}; do
   for u in 65 97; do
      chrv "$((i+u))" basechar
      chrv "$(((i+ekey-1)%26+u))" "lookup["$basechar"]"
   done
done

printf '%s' "Input (only ${ansi_lightgreen}letters$ansi_normal and ${ansi_lightgreen}spaces${ansi_normal}): "
IFS= read -re einput
read -n1 -rep "Do you want output to be uppercase? ${ansi_lightgreen}(y/n)$ansi_normal " oup
[[ ${oup,,} = y ]] && einput=${einput^^}

output=
linput=$einput
while [[ $linput ]]; do
   char=${linput::1}
   linput=${linput:1}
   if [[ ${lookup["$char"]} ]]; then
      # defined character
      output+=${lookup["$char"]}
   elif [[ $char = @(' '|$'\n') ]]; then
      # space and newline
      output+=$char
   else
      # other characters passed verbatim with warning message
      printf >&2 "Warning, character \`%s' not supported\n" "$char"
      output+=$char
   fi
done

printf 'Original text: %s\n' "$einput"
printf 'Encoded text: %s\n' "$output"

Pure Bash and no subshells :).

Upvotes: 1

tripleee
tripleee

Reputation: 189678

Because you don't quote the string, any whitespace in it will be lost. Try this instead.

echo "$einput" |
sed -e 's/\(.\)/\1\n/g' |
while IFS= read -r input; do
    eouput="$eouput${!input}"
    :
done

Depending on what your further processing looks like, I would perhaps just printf '%s' "${!input}" inside the loop, and process its output in a pipeline after done if necessary, to avoid yet another variable.

Upvotes: 1

glenn jackman
glenn jackman

Reputation: 247012

To iterate over the characters of a string, you can use parameter expansion:

eiput='Hello World!'
H="capital letter 'h'"
l="I appear 3 times"

strlen=${#eiput}
for ((i=0; i<strlen; i++)); do
    char=${eiput:i:1}
    printf "%2d:%c:%s\n" $i "$char" "${!char}"
done
 0:H:capital letter 'h'
 1:e:
 2:l:I appear 3 times
 3:l:I appear 3 times
 4:o:
 5: :
 6:W:
 7:o:
 8:r:
 9:l:I appear 3 times
10:d:
11:!:

Upvotes: 0

anubhava
anubhava

Reputation: 785581

You can use this in BASH:

while IFS= read -r ch; do 
    [[ "$ch" == " " ]] && echo "space character"
done < <(grep -o . <<< "$einput")
  • grep -o . <<< "$einput" to split input string character by character
  • < <(...) is called process substitution
  • IFS= to set input field separator to null (to allow space to be read by `read)

Upvotes: 1

Marc Bredt
Marc Bredt

Reputation: 955

change the internal field seperator from spaces to newlines using IFS=$'\n'.

Upvotes: 0

Related Questions