KSH - example requested of "\n" replacement in parameter expansion - ksh

I am looking for a working example of "\n" replacement in parameter expansion in KSH. The below is from the man page.
${parameter/pattern/string}
${parameter// pattern/string}
${parameter/#pattern/string}
${parameter/%pattern/string}
Expands parameter and replaces the longest match of pattern with the specified string. Each occurrence of \n in string is replaced by the portion of parameter that matches the nth sub-pattern.
Thanks! dvenus

The \n is not a newline but a numbered back reference.
As an example I start with a sed command:
par="String with =field1= and =field2= that you want to switch"
sed -r 's/(=.*=).*(=.*=).*/\2 and \1 switched./' <<< "${par}"
# result
String with =field2= and =field1= switched.
Without sed (ksh feature)
echo "${par/#(=[^=]*=)*#(=[^=]*=)*/\2 and \1 switched.}"

Related

bash shell reworking variable replace dots by underscore

I can't see to get it working :
echo $VERSIONNUMBER
i get : v0.9.3-beta
VERSIONNUMBERNAME=${VERSIONNUMBER:1}
echo $VERSIONNUMBERNAME
I get : 0.9.3-beta
VERSION=${VERSIONNUMBERNAME/./_}
echo $VERSION
I get : 0_9.3-beta
I want to have : 0_9_3-beta
I've been googling my brains out I can't make heads or tails of it.
Ideally I'd like to remove the v and replace the periods with underscores in one line.
Let's create your variables:
$ VERSIONNUMBER=v0.9.3-beta
$ VERSIONNUMBERNAME=${VERSIONNUMBER:1}
This form only replaces the first occurrence of .:
$ echo "${VERSIONNUMBERNAME/./_}"
0_9.3-beta
To replace all occurrences of ., use:
$ echo "${VERSIONNUMBERNAME//./_}"
0_9_3-beta
Because this approach avoids the creation of pipelines and subshells and the use of external executables, this approach is efficient. This approach is also unicode-safe.
Documentation
From man bash:
${parameter/pattern/string}
Pattern substitution. The pattern is expanded to produce a pattern
just as in pathname expansion. Parameter is expanded and the longest
match of pattern against its value is replaced with
string. If pattern begins with /, all matches of pattern are replaced
with string. Normally only the first match is replaced. If
pattern begins with #, it must match at the beginning of the expanded
value of parameter. If pattern begins with %, it must match at the
end of the expanded value of parameter. If string
is null, matches of pattern are deleted and the / following pattern
may be omitted. If the nocasematch shell option is enabled, the
match is performed without regard to the case of alphabetic
characters. If parameter is # or *, the substitution operation is
applied to each positional parameter in turn, and the
expansion is the resultant list. If parameter is an array variable
subscripted with # or *, the substitution operation is
applied to each member of the array in turn, and the expansion is the
resultant list.
(Emphasis added.)
You can combine pattern substitution with tr:
VERSION=$( echo ${VERSIONNUMBER:1} | tr '.' '_' )

sed command: search and replace variables with \n character

I have to variables in a bash script:
$string = "The cat is green.\n"
$line = "Sunny day today.\n"
each of those variables contain "\n" character, how can I use sed to search and replace:
sed 's/$string/$line/g' file.txt
This doesn't seem to work, if I erase the "\n" from the strings sed works properly.
If I had only the text I could escape "\n" by adding a backslash:
sed 's/"The cat is green.\\n"/"Sunny day today.\\n"/g' file.txt
How can I manage to do search/replace when variables contain "\n" in them.
Thank you for the help.
It looks like you are trying to match the two-character sequence \n, as opposed to the single newline character that together they represent in some contexts. There is a tremendous difference between these.
As part of your example, you presented
sed 's/$string/$line/g' file.txt
, but that won't work at all, because variable references are not expanded within single-quoted strings. That has nothing whatever to do with the values of shell variables string and line.
But let's consider those values:
$string="The cat is green.\n"
$line="Sunny day today.\n"
[Extra spaces removed.]
Of course, the problem you're focusing on is that sed recognizes \n as a code for a newline character, but you also have the problem that in a regular expression, the . character matches any character, so if you want it to be treated as a literal then it, too, needs to be escaped (in the pattern, but not in the replacement). If you're trying to support search and replace for arbitrary text, then there are other characters you'll need to escape, too.
Answering the question as posed (escaping only \n sequences) you might do this:
sed "s/${string//\\n/\\\\n}/${line//\\n/\\\\n}/g"
The ${foo//pat/repl} form of parameter expansion performs pattern substitution on the expanded value, but note well that the pattern (pat) is interpreted according to shell globbing rules, not as a regular expression. That specific form replaces every appearance of the pattern; read the bash manual for alternatives that match only the first appearance and/or that match only at the beginning or the end of the parameter's value. Note, too, the extra doubling of the \ characters in the pattern substitution -- they need to be escaped for the shell, too.
Given your variable definitions, that command would be equivalent to this:
sed 's/The cat is green.\\n/Sunny day today.\\n/g'
In other words, exactly what you wanted. Again, however, be warned: that is not a general solution for arbitrary search & replace. If you want that, then you'll want to study the sed manual to determine which characters need to be escaped in the regex, and which need to be escaped in the replacement. Moreover, I don't see a way to do it with just one pattern substitution for each variable.

SED not matching optional double quotes (")

I am trying to write a bash script to insert the word Test to any line starting with AlarmName:
Example:
AlarmName: "Blah"
Becomes
AlarmName: "TestBlah"
The double quotes are optional and here is the command I have so far
's/\(AlarmName:\s*("?)\([^"]*\)"?\)/<WHAT GOES HERE>/g' blah.txt > blah2.txt
I am running into two issues, the first is I can't seem to get SED to match the optional quotes. And the second issue is I am not sure how to modify text within a capture group
This should do it:
sed -E 's/^AlarmName:\s*"?/&Test/' file
It's enough to match the prefix part AlarmName: " and replace it with the copy of self (the & part), with Test text appended.
Or, if you prefer a pure POSIX BRE:
sed 's/^AlarmName:[[:space:]]*"\{0,1\}/&Test/' file
You can do the following:
sed 's|^\(AlarmName\s*:\s*\)\("*\)\([^"]*\)\("*\)|\1\2Test\3\4|'
You are breaking the input into four sections (\1, \2, \3 and \4), where:
\1 represents the AlarmName: prefix,
\2 represents the optional quotation,
\3 represents the string to prepend Test to, and
\4 represents the closing brace.
Notice the \'s before each of the group start/end braces (you missed some of those in your attempt above)

double backslashes of sed single-quoted command inside command substitutions get translated to a single backslash

printf '%s' 'abc' | sed 's/./\\&/g' #1, \a\b\c
printf '%s' "`printf '%s' 'abc' | sed 's/./\\&/g'`" #2, &&&
The expression inside the second backticks returns \a\b\c, and we have printf '%s' "\a\b\c", so it should print \a\b\c.
My question is: why does the second script print &&& ?
note:
I can get the second script work (prints \a\b\c) by prepending each backslash with another backslash, but I don't know why it's needed.
One related question:
why does this single quoted string get interpreted when it's inside of a command substitution
This is a good example to show difference between back-tick and $(cmd) command substitutions.
When the old-style backquoted form of substitution is used, backslash
retains its literal meaning except when followed by "$", "`", or "\".
The first backticks not preceded by a backslash terminates the command
substitution. When using the "$(COMMAND)" form, all characters between
the parentheses make up the command; none are treated specially.
http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_03_04.html
So take a look your example, I used echo instead of printf:
kent$ echo 'abc' | sed 's/./\\&/g'
\a\b\c
kent$ echo -E "`echo 'abc' | sed 's/./\\&/g'`"
&&&
kent$ echo -E "$(echo 'abc' | sed 's/./\\&/g')"
\a\b\c
You can see, the back-tick command substitution made your \\ as single \, thus together with the followed & it became \& (literal &)
Note that I used echo -E in order to disable the interpretation of backslash escapes so that the \a\b\c could be printed out.
Because on the second line :
you are saying:
printf '%s' 'abc' -> 'abc'
Then replace:
'abc'| sed 's/./\\&g' -> &&&
The s mean substitute
. mean one character
\\& by a char &
g mean multiple occurrence on the line
So you are saying:
Replace in abc each char by & multiple time on the same line
Explanation of \\\& :
Two backslashes become a single backslash in the shell which then in sed escapes the forward slash which is the middle delimiter.
\\& -> \& (which makes the forward & a regular character instead of a delimiter)
Three of them: The first two become one in the shell which then escape the third one in sed
\\\& -> \\&
Finally! don't forget that you command is under backquote:
The reason you have to escape it "twice" is because you're entering this command in an environment (such as a shell script) that interprets the double-quoted string once. It then gets interpreted again by the subshell.
From:
Why does sed require 3 backslashes for a regular backslash?

How truncate the ../ characters from string in bash?

How can I truncate the ../ or .. characters from string in bash
So, If I have strings
str1=../lib
str2=/home/user/../dir1/../dir2/../dir3
then how I can get string without any .. characters in a string like after truncated result should be
str1=lib
str2=/home/user/dir1/dir2/dir3
Please note that I am not interesting in absolute path of string.
You don't really need to fork a sub-shell to call sed. Use bash parameter expansion:
echo ${var//..\/}
str1=../lib
str2=/home/user/../dir1/../dir2/../dir3
echo ${str1//..\/} # Outputs lib
echo ${str2//..\/} # Outputs /home/user/dir1/dir2/dir3
You could use:
pax> str3=$(echo $str2 | sed 's?\.\./??g') ; echo $str3
/home/user/dir1/dir2/dir3
Just be aware (as you seem to be) that's a different path to the one you started with.
If you're going to be doing this infrequently, forking an external process to do it is fine. If you want to use it many times per second, such as in a tight loop, the internal bash commands will be quicker:
pax> str3=${str2//..\/} ; echo $str3
/home/user/dir1/dir2/dir3
This uses bash pattern substitution as described in the man page (modified slightly to adapt to the question at hand):
${parameter/pattern/string}
The parameter is expanded and the longest match of pattern against its value is replaced with string. If pattern begins with /, all matches of pattern are replaced with string.
If string is null, matches of pattern are deleted and the / following pattern may be omitted.
You can use sed to achieve it
sed 's/\.\.\///g'
For example
echo $str2 | sed 's/\.\.\///g'
OP => /home/user/dir1/dir2/dir3

Resources