Bash variable in sed command - bash

I need to add an header recursively to several file according to the name of the file.
So I have tried:
for i in *file
do
sed -i '1 i \A;B;C;D;E;F;G;H;${i%??}' a_${i} > header_a_${i}
done
the problem is that the variable reflecting the name of the file does not expand and in the header I have ${i%??} instead of part of the name file (%?? is to remove some ending characters).
Any help would be great.

Use double quotes:
sed '1 i\
A;B;C;D;E;F;G;H;'"${i%??}" a_${i} > header_a_${i}
It doesn't make any sense to use -i and to redirect the output, so I've omitted -i. Also, I've added an escaped newline after the insert command. Some sed do not require the newline, but many do. However, it seems odd to use sed for this. Instead, just do:
for i in *file; do
{ echo "A;B;C;D;E;F;G;H;${i%??}"; cat a_${i}; } > header_a_${i}
done

Related

Changing a line of text with sed with special characters

The name in the title says it all. However, I'm absolutely the worst with the sed command. So I'm trying to edit the following file:
/var/www/html/phpMyAdmin/config.inc.php
I want to edit the line that says
$cfg['Servers'][$i]['AllowRoot'] = false;
into the following
$cfg['Servers'][$i]['AllowRoot'] = true;
It has so many special characters and whatnot and I have no prior knowledge of how sed works. So here's some commands I've tried to specifically edit that one line.
sed -i "/*.AllowRoot.*/\$cfg['Servers'][\$i]['AllowRoot'] = true;/" /var/www/html/phpMyAdmin/config.inc.php
sed -i "/*.AllowRoot.*/$cfg['Servers'][$i]['AllowRoot'] = true;/" /var/www/html/phpMyAdmin/config.inc.php
# this one finds the line successfully and prints it so I know it's got the right string:
sed -n '/AllowRoot/p' /var/www/html/phpMyAdmin/config.inc.php
sed -i "s/'AllowRoot|false'/'AllowRoot|true'/" /var/www/html/phpMyAdmin/config.inc.php
I have absolutely no idea what I'm doing and I'm not learning a whole lot besides the feeling that the last command splits up 'AllowRoot|false' makes sure that both must be present in the sentence to come back as a result. So to my logic, I thought changing the word false into true would make that happen, but nothing. The other commands return... bizarre results at best, one even emptying the file. Or that's one of the commands I had not written down here, I've lost track after 50 attempts. What is the solution here?
The [ and ] need to be escaped to match literal brackets, instead of inadvertently starting a bracket expression. This should work:
$ sed -i "/\$cfg\['Servers'\]\[\$i\]\['AllowRoot'\]/s/false/true/" /var/www/html/phpMyAdmin/config.inc.php
There is not many things to escape in sed. Main problem in your line is / which you have chosen as delimiter (most common, but not required). I suggest you use # and the following will work:
sed -i "s#$cfg['Servers'][$i]['AllowRoot'] = false;<br />#$cfg['Servers'][$i]['AllowRoot'] = true;<br />#g" input.txt
however you need to think about bash interpreter as well. $i and $cfg will be interpreted as variables. My suggestion is that when you want to match a string like this to put the sed expression in a text file like this:
cat allow_root_true.sed
s#['Servers'][]['AllowRoot'] = false;<br />#['Servers'][]['AllowRoot'] = true;<br />#g
and run the command using sed -f like this:
sed -i -f allow_root_true.sed input.txt
Warning -i will change the input file
sed can't do literal string matching which is why you need to escape so many characters (see Is it possible to escape regex metacharacters reliably with sed), but awk can:
$ awk -v str="\$cfg['Servers'][\$i]['AllowRoot']" 'index($0,str){sub(/false/,"true")} 1' file
//some text here
$cfg['Servers'][$i]['AllowRoot'] = true;<br />
//some more text here
Run code snippetHide resultsExpand snippet
In the above we only have to escape the $s to protect them from the shell since the string is enclosed in "s to allow it to include 's.

Inserting contents of a file with special characters using sed

I want to replace a marker (REPLACETHIS) in file1.txt with the entire contents of file2.txt, which will include newlines and special characters.
An example of file2.txt's contents would be
<Location />
Order deny,allow
Deny from all
Allow from 1.2.3.4
Allow from 5.6.7.8
</Location>
My general code, minus handling of special characters, would look something like this:
value=$(</home/name/scripts/file2.txt)
sed -i -e "s|REPLACETHIS|$value|" /home/name/scripts/file1.txt
What's the best way to go about handling this?
In sed, the best option for inserting text from file would be to use the r file command:
sed -i -e '/REPLACETHIS/{r /path/to/file2.txt' -e ';d;}' file1.txt
or, in the expanded form:
sed -i '/REPLACETHIS/ {
r /path/to/file2.txt
d
}' file1.txt
The r file command will read the text from file and insert it into the output stream. To also delete the REPLACETHIS text, we need the delete d command (heads-up: this will delete the complete line containing the text REPLACETHIS; if you need to use a mid-text marker, you could replace the d with s///, as noted by #ghoti).
In the first example, we had to break the sed program in two expressions, the reason being that r command has to end with a newline. The alternative is to write the program in several lines, as in the expanded example.
Also note that BSD sed handles -i option differently from GNU sed. The above will work in GNU, but if you need it for BSD, you should write: -i '' instead of -i.
One doesn't strictly need to use sed for this at all.
file_to_change=/home/name/scripts/file1.txt
value=$(</home/name/scripts/file2.txt)
infile=$(<"$file_to_change")
tempfile=$(mktemp "$file_to_change.XXXXXX")
if printf '%s\n' "${infile//REPLACETHIS/$value}" >"$tempfile"; then
mv -- "$tempfile" "$file_to_change"
else
rm -f -- "$tempfile"
fi
This works even without sed -i (which is a nonstandard, nonportable, and incompatible between common implementations).

Bash string operations unwanted remove new line characters

I'm completely new to bash scripting so excuse me....
I am trying to combine some html content with a template that contains standard headings, the template has a place-holder "REPLACEME" which I thought I could just find and replace on. The loop simply repeats the operation on all the files in the directory.
REPLACEME="REPLACEME"
for file in *.html
do
TEMPLATE=$(<../template/template.html)
CONTENT=$(<$file)
OUTPUT="${TEMPLATE/"$REPLACEME"/"$CONTENT"}"
echo $OUTPUT > ../compiled/$file
done
This works but the resulting html file has been stripped of new line characters, which makes it look like junk! Can anyone help?
Replace:
echo $OUTPUT > ../compiled/$file
With:
echo "$OUTPUT" > ../compiled/$file
The shell performs word splitting on unquoted variables. With the default value for IFS, this means that all sequences of whitespace, which includes tabs and newlines, are replaced with a single blank. To prevent that, put the variable in double-quotes as shown above.
Using sed you could achieve it like below :
sed -i 's/REPLACEME/new_text/g' /path/to/your/template.html
The -i option in sed is for inplace edit & the g option is for global substitution.
Edit 1:
If you need to use a variable inside sed you can do it this way
var="Sometext";
sed -i "s/REPLACEME/$var/g" /path/to/your/template.html
Mind the double quotes here, it makes the shell expand variables.
If your system supports gnu-awk (gawk) you may achieve the above with
gawk '
{
$0=gensub(/REPLACEME/"NEWTEXT","g",$0)
printf "%s\n", $0
}' < /path/to/your/template.html > newtemplate.html && mv newtemplate.html template.html

bash shell to add a new line in a file after a pattern match found [duplicate]

This question already has answers here:
Insert line after match using sed
(8 answers)
Closed 8 years ago.
I have a file ".gitignore" contains various source file name as
src/abc
src/line
src/another
I like to add another line "src/line.cpp" after a match found "src/line"
result would look like as
src/abc
src/line
src/line.cpp
src/another
I am using sed as
set -- "$File" // $File contains src/line
IFS="/"; declare -a Array=($*)
echo "${Array[0]}" // This prints src
echo "${Array[1]}" // This prints line
sed -i '/$Array[0]\/$Array[1]/a $Array[0]\/$Array[1].cpp' $File
The sed command is not working.
I have a feeling that slashes are not properly handled. If I hard code as
sed -i '/src\/line/a src\/line.cpp' $File
then it works.
Any solutions? Thanks in advance!
You properly use double-quotes in echo "${Array[1]}", and the braces {} needed for the array index. But you neglect to do so in your sed line. So try:
sed -i "/${Array[0]}\/${Array[1]} *$/a ${Array[0]}\/${Array[1]}.cpp" .gitignore
I've added an extra $ here for the end of the line, so:
Repeating the command leaves the file unchanged
Reduces risk of inadvertent matches
The space-star-dollar *$ handles the case where a line has blank spaces at the end. You may also want to add a ^ to match the start of line.
Also, your sed command operates on $File; I think you want .gitignore.
But rather than fiddle around with IFS and arrays, it might be cleaner to use bash's parameter substitution. Replace ${Array[0]} and ${Array[1]} with:
${File%/*}
${File#*/}
Finally, you don't even need to do that, if you replace the sed slash / with for example # (but underscore _ would be bad, as it is commonly used in filenames).
So the penultimate command line is:
sed -i "\#$File#a $File.cpp" .gitignore
or, to handle blanks at the end of a line and to avoid spurious matches:
sed -i "\#$File *\$#a $File.cpp" .gitignore
Note only the first # is escaped.
With GNU sed, you can do it like:
sed -i.BAK 's_src/line_\0\n\0.txt_'
As it allows to use other separators than slashes.
You can use it this way:
s='src/line'; r='src/line.cpp'; sed "s~$s~&\n$r~" file
src/abc
src/line
src/line.cpp
src/another

How to append to specific lines in a flat file using shell script

I have a flat file that contains something like this:
11|30646|654387|020751520
11|23861|876521|018277154
11|30645|765418|016658304
Using shell script, I would like to append a string to certain lines in this file, if those lines contain a specific string.
For example, in the above file, for lines containing 23861, I would like to append a string "Processed" at the end, so that the file becomes:
11|30646|654387|020751520
11|23861|876521|018277154|Processed
11|30645|765418|016658304
I could use sed to append the string to all lines in the file, but how do I do it for specific lines ?
I'd do it this way
sed '/\|23861\|/{s/$/|Something/;}' file
This is similar to Marcelo's answer but doesn't require extended expressions and is, I think, a little cleaner.
First, match lines having 23861 between pipes
/\|23861\|/
Then, on those lines, replace the end-of-line with the string |Something
{s/$/|Something/;}
If you want to do more than one of these you could simply list them
sed '/\|23861\|/{s/$/|Something/;};/\|30645\|/{s/$/|SomethingElse/;}' file
Use the following awk-script:
$ awk '/23861/ { $0=$0 "|Processed" } {print}' input
11|30646|654387|020751520
11|23861|876521|018277154|Processed
11|30645|765418|016658304
or, using sed:
$ sed 's/\(.*23861.*$\)/\1|Processed/' input
11|30646|654387|020751520
11|23861|876521|018277154|Processed
11|30645|765418|016658304
Use the substitution command:
sed -i~ -E 's/(\|23861\|.*)/\1|Processed/' flat.file
(Note: the -i~ performs the substitution in-place. Just leave it out if you don't want to modify the original file.)
You can use the shell
while read -r line
do
case "$line" in
*23681*) line="$line|Processed";;
esac
echo "$line"
done < file > tempo && mv tempo file
sed is just a stream version of ed, which has a similar command set but was designed to edit files in place (allegedly interactively, but you wouldn't want to use it that way unless all you had was one of these). Something like
field_2_value=23861
appended_text='|processed'
line_match_regex="^[^|]*|$field_2_value|"
ed "$file" <<EOF
g/$line_match_regex/s/$/$appended_text/
wq
EOF
should get you there.
Note that the $ in .../s/$/... is not expanded by the shell, as are $line_match_regex and $appended_text, because there's no such thing as $/ - instead it's passed through as-is to ed, which interprets it as text to substitute ($ being regex-speak for "end of line").
The syntax to do the same job in sed, should you ever want to do this to a stream rather than a file in place, is very similar except that you don't need the leading g before the regex address:
sed -e "/$line_match_regex/s/$/$appended_text/" "$input_file" >"$output_file"
You need to be sure that the values you put in field_2_value and appended_text never contain slashes, because ed's g and s commands use those for delimiters.
If they might do, and you're using bash or some other shell that allows ${name//search/replace} parameter expansion syntax, you could fix them up on the fly by substituting \/ for every / during expansion of those variables. Because bash also uses / as a substitution delimiter and also uses \ as a character escape, this ends up looking horrible:
appended_text='|n/a'
ed "$file" <<EOF
g/${line_match_regex//\//\\/}/s/$/${appended_text//\//\\/}/
wq
EOF
but it does work. Nnote that both ed and sed require a trailing / after the replacement text in s/search/replace/ while bash's ${name//search/replace} syntax doesn't.

Resources