changing position of character in string bash - string

i have this string
E="ABCDEFGHIJKLMNOPQRSTUVWXYZ"
any idea how to change the position of the letters with its neighbour if the user enters no
and it will continue changing position until user satisfied with the string OR it has reach end of the string.
is the position of 1st correct? Y/N
N
E=BACDEFGHIJKLMNOPQRSTUVWXYZ
*some of my code here*
are u satisfied? Y/N
N
is the position of 2nd correct? Y/N
N
E=BCADEFGHIJKLMNOPQRSTUVWXYZ
*some of my code here*
are u satisfied? Y/N
N
is the position 3rd correct? Y?N
Y
E=BCADEFGHIJKLMNOPQRSTUVWXYZ
*some of my code here*
are u satisfied? Y/N
N
is the position 4th correct? Y?N
Y
E=BCADEFGHIJKLMNOPQRSTUVWXYZ
*some of my code here*
are u satisfied? Y/N
Y
*exit prog*
any help will greatly appreciated. thanks
edited
i got this code from a forum. worked perfectly. but any idea how to swap next character after it have done once? for example ive done the first position, and i want to run it for the second character? any idea?
dual=ETAOINSHRDLCUMWFGYPBVKJXQZ
phrase='E'
rotat=1
newphrase=$(echo $phrase | tr "${dual:0:26}" "${dual:${rotat}:26}")
echo ${newphrase}

You will have to use a loop.
#!/bin/bash
E="ABCDEFGHIJKLMNOPQRSTUVWXYZ"
echo "$E"
for (( i = 1; i < ${#E}; i++ )); do
echo "Is position $i correct? Y/N"
read answer
if [ "$answer" == "N" -o "$answer" == "n" ]
then
E="${E:0:$i-1}${E:$i:1}${E:$i-1:1}${E:$i+1}"
fi
echo "$E"
echo "Are you satisfied? Y/N"
read answer
if [ "$answer" == "Y" -o "$answer" == "y" ]
then
break
fi
done
The loop iterates over every character of the string. The string altering happens in the first if clause. It's nothing more than basic substring operations. ${E:n} returns the substring of E starting at position n. ${E:n:m} returns the next m characters of E starting at position n . The remaining lines are the handling if the user is satisfied and wants to exit.

With bash, you can extract a substring easily:
${string:position:length}
This syntax allows you to use variable extensions, so it is quite straightforward to swap two consective characters in a string:
E="${dual:0:$rotat}${dual:$((rotat+1)):1}${dual:$rotat:1}${dual:$((rotat+2))}"
Arithmetics may need to be enclosed into $((...)).

From bash man pages:
${parameter:offset}
${parameter:offset:length}
Substring Expansion. Expands to up to length characters of parameter starting at the character specified by offset. If
length is omitted, expands to the substring of parameter starting at the character specified by offset. length and offset are
arithmetic expressions (see ARITHMETIC EVALUATION below). length must evaluate to a number greater than or equal to zero. If
offset evaluates to a number less than zero, the value is used as an offset from the end of the value of parameter. If param-
eter is #, the result is length positional parameters beginning at offset. If parameter is an array name indexed by # or *,
the result is the length members of the array beginning with ${parameter[offset]}. A negative offset is taken relative to one
greater than the maximum index of the specified array. Note that a negative offset must be separated from the colon by at
least one space to avoid being confused with the :- expansion. Substring indexing is zero-based unless the positional parame-
ters are used, in which case the indexing starts at 1.
Examples:
pos=5
E="ABCDEFGHIJKLMNOPQRSTUVWXYZ"
echo "${E:pos:1} # print 6th character (F)
echo "${E:pos} # print from 6th character (F) to the end
What dou you mean when you say "its neighbour"? Excepting first and last characters, every character in the string has two neighbours.
To exchange the "POS" character (starting from 1) and its next one (POS+1):
E="${E:0:POS-1}${E:POS:1}${E:POS-1:1}${E:POS+1}"

Related

Bash String Format Comparison with Wildcards

I am fairly new to bash scripting and was trying to echo only lines that match a specific formatting. I have this code so far:
LINE=1
while read -r CURRENT_LINE
do
if [[ $CURRENT_LINE == ??-?-??? ]]
then
echo "$LINE: $CURRENT_LINE"
fi
((LINE++))
done < "./new-1.txt"
The text file contains number sequences on each line that match the following format: "12-3-456", but also contains sequences that are in different formats as well, such as "123-89203-9420" or "123-456-7890". I can't quite understand why the if statement inside the while loop does not result to True on lines that match the formatting. I've tried using the * as well, but using it gives me incorrect results.
Here are the contents of the text file new-1.txt. I want the script to output "Line 1: 11-1-111", but it doesn't output anything.
11-1-111
222-22-2222
333-33-3333
444-444-4444
555-555-5555
In the regex parlance, the ? makes the character or selection optional, ie , a character/selection is allowed to occur at most one time but zero occurrences are also tolerated.
However, the == operation is not the regex matching operator. It is =~.
So changing your if clause to the below would do the job.
[[ $CURRENT_LINE =~ "^[0-9]{2}-[0-9]{1}-[0-9]{3}$" ]]
Here
The ^ specifies the beginning of regex and $ the end. So we have a tight coupling of the pattern to match
[0-9] denotes a range, here any number from zero to nine.
The {n} mandates that the preceding character/selection should match exactly n number of times
Note : You can also use a more verbose [[:digit:]] instead of [0-9]

Extracting a string from a substring in bash (yes, that way around)

I have a string of several words in bash called comp_line, which can have any number of spaces inside. For example:
"foo bar apple banana q xy"
And I have a zero-based index comp_point pointing to one character in that string, e.g. if comp_point is 4, it points to the first 'b' in 'bar'.
Based on the comp_point and comp_line alone, I want to extract the word being pointed to by the index, where the "word" is a sequence of letters, numbers, punctuation or any other non-whitespace character, surrounded by whitespace on either side (if the word is at the start or end of the string, or is the only word in the string, it should work the same way.)
The word I'm trying to extract will become cur (the current word)
Based on this, I've come up with a set of rules:
Read the current character curchar, the previous character prevchar, and the next character nextchar. Then:
If curchar is a graph character (non-whitespace), set cur to the letters before and after curchar (stopping until you reach a whitespace or string start/end on either side.)
Else, if prevchar is a graph character, set cur to the letters from the previous letter, backwards until the previous whitespace character/string start.
Else, if nextchar is a graph character, set cur to the letters from the next letter, forwards until the next whitespace character/string end.
If none of the above conditions are hit (meaning curchar, nextchar and prevchar are all whitespace characters,) set cur to "" (empty string)
I've written some code which I think achieves this. Rules 2, 3 and 4 are relatively straightforward, but rule 1 is the most difficult to implement - I've had to do some complicated string slicing. I'm not convinced that my solution is in any way ideal, and want to know if anyone knows of a better way to do this within bash only (not outsourcing to Python or another easier language.)
Tested on https://rextester.com/l/bash_online_compiler
#!/bin/bash
# GNU bash, version 4.4.20
comp_line="foo bar apple banana q xy"
comp_point=19
cur=""
curchar=${comp_line:$comp_point:1}
prevchar=${comp_line:$((comp_point - 1)):1}
nextchar=${comp_line:$((comp_point + 1)):1}
echo "<$prevchar> <$curchar> <$nextchar>"
if [[ $curchar =~ [[:graph:]] ]]; then
# Rule 1 - Extract current word
slice="${comp_line:$comp_point}"
endslice="${slice%% *}"
slice="${slice#"$endslice"}"
slice="${comp_line%"$slice"}"
cur="${slice##* }"
else
if [[ $prevchar =~ [[:graph:]] ]]; then
# Rule 2 - Extract previous word
slice="${comp_line::$comp_point}"
cur="${slice##* }"
else
if [[ $nextchar =~ [[:graph:]] ]]; then
# Rule 3 - Extract next word
slice="${comp_line:$comp_point+1}"
cur="${slice%% *}"
else
# Rule 4 - Set cur to empty string ""
cur=""
fi
fi
fi
echo "Cur: <$cur>"
The current example will return 'banana' as comp_point is set to 19.
I'm sure that there must be a neater way to do it that I hadn't thought of, or some trick that I've missed. Also it works so far, but I think there may be some edge cases I hadn't thought of. Can anyone advise if there's a better way to do it?
(The XY problem, if anyone asks)
I'm writing a tab completion script, and trying to emulate the functionality of COMP_WORDS and COMP_CWORD, using COMP_LINE and COMP_POINT. When a user is typing a command to tab complete, I want to work out which word they are trying to tab complete just based on the latter two variables. I don't want to outsource this code to Python because performance takes a big hit when Python is involved in tab complete.
Another way in bash without array.
#!/bin/bash
string="foo bar apple banana q xy"
wordAtIndex() {
local index=$1 string=$2 ret='' last first
if [ "${string:index:1}" != " " ] ; then
last="${string:index}"
first="${string:0:index}"
ret="${first##* }${last%% *}"
fi
echo "$ret"
}
for ((i=0; i < "${#string}"; ++i)); do
printf '%s <-- "%s"\n' "${string:i:1}" "$(wordAtIndex "$i" "$string")"
done
if anyone knows of a better way to do this within bash only
Use regexes. With ^.{4} you can skip the first four letters to navigate to index 4. With [[:graph:]]* you can match the rest of the word at that index. * is greedy and will match as many graphical characters as possible.
wordAtIndex() {
local index=$1 string=$2 left right indexFromRight
[[ "$string" =~ ^.{$index}([[:graph:]]*) ]]
right=${BASH_REMATCH[1]}
((indexFromRight=${#string}-index-1))
[[ "$string" =~ ([[:graph:]]*).{$indexFromRight}$ ]]
left=${BASH_REMATCH[1]}
echo "$left${right:1}"
}
And here is full test for your example:
string="foo bar apple banana q xy"
for ((i=0; i < "${#string}"; ++i)); do
printf '%s <-- "%s"\n' "${string:i:1}" "$(wordAtIndex "$i" "$string")"
done
This outputs the input string vertically on the left, and on each index extracts the word that index points to on the right.
f <-- "foo"
o <-- "foo"
o <-- "foo"
<-- ""
b <-- "bar"
a <-- "bar"
r <-- "bar"
<-- ""
<-- ""
<-- ""
a <-- "apple"
p <-- "apple"
p <-- "apple"
l <-- "apple"
e <-- "apple"
<-- ""
<-- ""
b <-- "banana"
a <-- "banana"
n <-- "banana"
a <-- "banana"
n <-- "banana"
a <-- "banana"
<-- ""
q <-- "q"
<-- ""
x <-- "xy"
y <-- "xy"

Bash: How to check if the last three string characters equals '***'

I know how to do it for one character:
[ "${filename: -1}" == "*" ]
Is it possible to do it for more?
Why have you stopped in the -1 value?
The manual pages of bash give the answer:
${parameter:offset}
${parameter:offset:length}
If offset evaluates to a number less than zero, the value is used as
an offset in characters from the end of the value of parameter. If
length evaluates to a number less than zero, it is interpreted as an
offset in characters from the end of the value of parameter rather
than a number of characters, and the expansion is the characters
between offset and that result. Note that a negative offset must be
separated from the colon by at least one space to avoid being confused
with the ‘:-’ expansion.
Therefore
[ "${filename: -3}" == "***" ]

Bash script string processing

I wrote a script that reads a Plain text and a key, and then loops trough each character of plain text and shifts it with the value of the corresponding character in key text, with a=0 b=1 c=2 ... z = 25
the code works fine but with a string of size 1K characters it takes almost 3s to execute.
this is the code:
small="abcdefghijklmnopqrstuvwxyz" ## used to search and return the position of some small letter in a string
capital="ABCDEFGHIJKLMNOPQRSTUVWXYZ" ## used to search and return the position of some capital letter in a string
read Plain_text
read Key_text
## saving the length of each string
length_1=${#Plain_text}
length_2=${#Key_text}
printf " Your Plain text is: %s\n The Key is: %s\n The resulting Cipher text is: " "$Plain_text" "$Key_text"
for(( i=0,j=0;i<$length_1;++i,j=`expr $(($j + 1)) % $length_2` )) ## variable 'i' is the index for the first string, 'j' is the index of the second string
do
## return a substring statring from position 'i' and with length 1
c=${Plain_text:$i:1}
d=${Key_text:$j:1}
## function index takes two parameters, the string to seach in and a substring,
## and return the index of the first occerunce of the substring with base-insex 1
x=`expr index "$small" $c`
y=`expr index "$small" $d`
##shifting the current letter to the right with the vaule of the corresponding letter in the key mod 26
z=`expr $(($x + $y - 2)) % 26`
##print the resulting letter from capital letter string
printf "%s" "${capital:$z:1}"
done
echo ""
How is it possible to improve the performance of this code.
Thank you.
You are creating 4 new processes in each iteration of your for loop by using command substitution (3 substitutions in the body, 1 in the head). You should use arithmetic expansion instead of calling expr (search for $(( in the bash(1) manpage). Note that you don't need the $ to substitute variables inside $(( and )).
you can change character like this
a=( soheil )
echo ${a/${a:0:1}/${a:1:1}}
for change all char use loop like for
and for change char to upper
echo soheil | tr "[:lower:]" "[:upper:]"
i hope i understand your question.
be at peace
You will have a lot of repeating chars in a 1K string.
Imagine the input was 1M.
You should calculate all request/respond pairs in front, so your routine only has to lookup the replacement.
I would think of a solution with arrays is the best approach here.

Bash: ${string:$i:1} what does this mean?

This is the script. It reverses a string entered by the user:
#!/bin/bash
read -p "Enter string:" string
len=${#string}
for (( i=$len-1; i>=0; i-- ))
do
# "${string:$i:1}"extract single single character from string.
reverse="$reverse${string:$i:1}"
done
echo "$reverse"
I don't understand the following part of the script. What is this? Looks like some kind of extended variable interpolation.
${string:$i:1}
in bash doing something lik this: ${string:3:1} means: take substring starting from the character at pos 3 (0-based, so the 4th character), and length = 1 character.
for example:
string=abc
then ${string:0:1} equals a and ${string:2:1} equals c.
This script takes the value of the variable $i: so it just takes the character at position $i.
It's substring expansion.
from the man pages:
${parameter:offset:length}
Substring Expansion. Expands to up to length characters of parameter starting at the character specified by offset. If length is omitted, expands to the
substring of parameter starting at the character specified by offset. length and offset are arithmetic expressions (see ARITHMETIC EVALUATION below). If
offset evaluates to a number less than zero, the value is used as an offset from the end of the value of parameter. Arithmetic expressions starting with a -
must be separated by whitespace from the preceding : to be distinguished from the Use Default Values expansion. If length evaluates to a number less than
zero, and parameter is not # and not an indexed or associative array, it is interpreted as an offset from the end of the value of parameter rather than a
number of characters, and the expansion is the characters between the two offsets. If parameter is #, the result is length positional parameters beginning
at offset. If parameter is an indexed array name subscripted by # or *, the result is the length members of the array beginning with ${parameter[offset]}.
A negative offset is taken relative to one greater than the maximum index of the specified array. Substring expansion applied to an associative array proâ
duces undefined results. Note that a negative offset must be separated from the colon by at least one space to avoid being confused with the :- expansion.
Substring indexing is zero-based unless the positional parameters are used, in which case the indexing starts at 1 by default. If offset is 0, and the posiâ
tional parameters are used, $0 is prefixed to the list.

Resources