How can I prepend a string to the beginning of each line in a file? - linux

I have the following bash code which loops through a text file, line by line .. im trying to prefix the work 'prefix' to each line but instead am getting this error:
rob#laptop:~/Desktop$ ./appendToFile.sh stusers.txt kp
stusers.txt
kp
./appendToFile.sh: line 11: /bin/sed: Argument list too long
115000_210org#house.com,passw0rd
This is the bash script ..
#!/bin/bash
file=$1
string=$2
echo "$file"
echo "$string"
for line in `cat $file`
do
sed -e 's/^/prefix/' $line
echo "$line"
done < $file
What am i doing wrong here?
Update:
Performing head on file dumps all the lines onto a single line of the terminal, probably related?
rob#laptop:~/Desktop$ head stusers.txt
rob#laptop:~/Desktop$ ouse.com,passw0rd

a one-line awk command should do the trick also:
awk '{print "prefix" $0}' file

Concerning your original error:
./appendToFile.sh: line 11: /bin/sed: Argument list too long
The problem is with this line of code:
sed -e 's/^/prefix/' $line
$line in this context is file name that sed is running against. To correct your code you should fix this line as such:
echo $line | sed -e 's/^/prefix/'
(Also note that your original code should not have the < $file at the end.)
William Pursell addresses this issue correctly in both of his suggestions.
However, I believe you have correctly identified that there is an issue with your original text file. dos2unix will not correct this issue, as it only strips the carriage returns Windows sticks on the end of lines. (However, if you are attempting to read a Linux file in Windows, you would get a mammoth line with no returns.)
Assuming that it is not an issue with the end of line characters in your text file, William Pursell's, Andy Lester's, or nullrevolution's answers will work.
A variation on the while read... suggestion:
while read -r line; do echo "PREFIX " $line; done < $file
This could be run directly from the shell (no need for a batch / script file):
while read -r line; do echo "kp" $line; done < stusers.txt

The entire loop can be replaced by a single sed command that operates on the entire file:
sed -e 's/^/prefix/' $file

A Perl way to do it would be:
perl -p -e's/^/prefix' filename
or
perl -p -e'$_ = "prefix $_"' filename
In either case, that reads from filename and prints the prefixed lines to STDOUT.
If you add a -i flag, then Perl will modify the file in place. You can also specify multiple filenames and Perl will magically do all of them.

Instead of the for loop, it is more appropriate to use while read...:
while read -r line; do
do
echo "$line" | sed -e 's/^/prefix/'
done < $file
But you would be much better off with the simpler:
sed -e 's/^/prefix/' $file

Use sed. Just change the word prefix.
sed -e 's/^/prefix/' file.ext
If you want to save the output in another file
sed -e 's/^/prefix/' file.ext > file_new.ext

You don't need sed, just concatenate the strings in the echo command
while IFS= read -r line; do
echo "prefix$line"
done < filename
Your loop iterates over each word in the file:
for line in `cat file`; ...

sed -i '1a\
Your Text' file1 file2 file3

A solution without sed/awk and while loops:
xargs -n1 printf "$prefix%s\n" < "$file"

Related

How to compare filenames in two text files on Linux bash?

I have two lists list1 and list2 with a filename on each line. I want a result with all filenames that are only in list2 and not in list1, regardless of specific file extensions (but not all). Using Linux bash, any commands that do not require any extra installations. In the example lists, I do know all file extensions that I wish to ignore. I made an attempt but it does not work at all, I don't know how to fix it. Apologies for my inexperience.
I wish to ignore the following extensions:
.x
.xy
.yx
.y
.jpg
list1.txt
text.x
example.xy
file.yx
data.y
edit
edit.jpg
list2.txt
text
rainbow.z
file
data.y
sunshine
edit.test.jpg
edit.random
result.txt
rainbow.z
sunshine
edit.test.jpg
edit.random
My try:
while read LINE
do
line2=$LINE
sed -i 's/\.x$//g' $LINE $line2
sed -i 's/\.xy$//g' $LINE $line2
sed -i 's/\.yx$//g' $LINE $line2
sed -i 's/\.y$//g' $LINE $line2
then sed -i -e '$line' result.txt;
fi
done < list2.txt
Edit: I forgot two requirements. The filenames can have . in them and not all filenames must have an extension. I know the extensions that must be ignored. I ammended the lists accordingly.
An awk solution might be more efficient for this task:
awk '
{ f=$0; sub(/\.(xy?|yx?|jpg)$/,"",f) }
NR==FNR { a[f]; next }
!(f in a)
' list1.txt list2.txt > result.txt
comm can do precisely this.
You can preprocess the input:
strip the suffices
sort (comm expects sorted input)
remove duplicates
ss()( sed 's/\.\(x\|xy\|yx\|y\|jpg\)$//' "$#" | sort -u )
comm -13 <(ss list1.txt) <(ss list2.txt) >result.txt
Your code was:
while read LINE
do
line2=$LINE
sed -i 's/\.x$//g' $LINE $line2
sed -i 's/\.xy$//g' $LINE $line2
sed -i 's/\.yx$//g' $LINE $line2
sed -i 's/\.y$//g' $LINE $line2
then sed -i -e '$line' result.txt;
fi
done < list2.txt
Some issues that immediately jump out:
syntax error - then/fi but no matching if
you never access list1
you don't quote variables when you use them, so whitespace and special characters will cause problems
while read ... sed ... sed ... sed ... is inefficient - multiple invocations of sed instead of just one, and a loop that sed would perform implicitly
sed expects file arguments not strings
sed -i will try to overwrite input file arguments
you use result.txt as both input and output to sed but never assign any contents to it
you try to use data ($line) as sed commands, instead of applying sed commands to that data
because you used single-quotes, sed -i -e '$line' will attempt to run a (non-existent) sed command line on the last line of input ($)
g option to s/// does nothing when search is anchored
I'd use join:
$ join -t. -j1 -v2 -o 2.1,2.2 <(sort list1.txt) <(sort list2.txt) | sed 's/\.$//'
rainbow.z
sunshine
(The bit of sed is needed to turn sunshine. into sunshine)

How to read lines and create files removing https://

I want a bash script to read a file with n line and do the following
1.Pass the line as input to a python file ( Its done, no issues )
2.Create a file and redirect the output of the python
for line in $(cat file.txt)
do
touch $line-links
python file.py $line > $line-links
done
the problem is file.txt consists of links in the form of https://www.example.com.
And when I execute the bash, it's throwing an error
touch: https://www.example.com-links.txt: No such file or directory
I realized I have to remove the https:// portion, but how to create the file removing https:// from the line
You can use a simple variable expansion.
See:
for line in $(cat file.txt)
do
url=${line##*//}
touch ${url}-links
python file.py $ > ${url}-links
done
You can use this python script. Save this as update_line.py and then python update_line.py. This will remove all the https:// in the file
fpath = 'file.txt'
with open(fpath, 'r+') as f:
lines = f.readlines()
f.seek(0)
f.truncate()
for line in lines:
line = line.replace('https://', '')
f.write(line)
for line in $(cat file.txt)
do
touch ${line-links/https:\/\//}
python file.py $line > ${line-links/https:\/\//}
done
You could use sed command to accomplish the task
for line in $(cat file.txt)
do
variable=$( echo 'https://www.example.com' | sed 's/https\?:\/\///') | touch $variable // Using Sed to strip the https://
$python file.py $line > $line-links
done
You can use a while read loop and parameter expansion.
while IFS= read -r url; do
url=${url#*${url%%':'*}}
url=${url#*'//'}
> "$url"
python file.py "$url" > "${url}-links"
done < file.txt
I figured out the solution in a different way
for line in $(cat file.txt)
do
echo $line | cut -d"/" -f3 | grep "\S" | xargs touch
python file.py $line > $line
done
grep "\S" is used to remove blank lines , the output will be domain.com file created
There are many options, this is one of them :)
for line in $(cat file.txt)
do
domain=$( echo $line | awk -F '//' {'print $2'} )
touch "$domain-links"
$python file.py $line > $domain-links
done

pipe then hyphen (stdin) as an alternative to for loop

I wrote a few sed an awk commands to extract a set of IDs that are associated with file names. I would like to run a set of commands using these filenames from id.txt
cat id.txt
14235.gz
41231.gz
41234.gz
I usually write for loops as follows:
for i in $(cat id.txt);
do
command <options> $i
done
I thought I could also do cat id.txt | command <options> -
Is there a way to pipe the output of cat, awk, sed, etc, line by line into a command?
Use a while read loop see Don't read lines wit for
while IFS= read -r line_in_text_file; do
echo "$line_in_text_file"
done < id.txt
Commands don't usually get their filename arguments on standard input. Using - as an argument means to read the file contents from standard input instead of a named file, it doesn't mean to get the filename from stdin.
You can use command substitution to use the contents of the file as all the filename arguments to the command:
command <options> $(cat id.txt)
or you can use xargs
xargs command <options> < id.txt
Is there a way to pipe the output of cat, awk, sed, etc, line by line into a command?
Compound commands can be placed in a pipe, the syntax is not very strict. The usual:
awk 'some awk script' |
while IFS= read -r line; do
echo "$line"
done |
sed 'some sed script'
I avoid reading input line by line using a while read - it's very slow. It's way faster to use awk scripts and other commands.
Command groups can be used to:
awk 'some awk script' |
{ # or '(', but there is no need for a subshell
echo "header1,header2"
# remove first line
IFS= read -r first_line
# ignore last line
sed '$d'
} |
sed 'some sed script'
Remember that pipe command are run in a subshell, so variable changes will not affect parent shell.
Bash has process substitution extension that let's you run a while loop inside parent shell:
var=1
while IFS= read -r line; do
if [[ "$line" == 2 ]]; then
var=2
fi
done <(
seq 10 |
sed '$d'
)
echo "$var" # will output 2
xargs can do this
cat id.txt | xargs command
From xargs help
$ xargs --help
Usage: xargs [OPTION]... COMMAND [INITIAL-ARGS]...
Run COMMAND with arguments INITIAL-ARGS and more arguments read from input.
Mandatory and optional arguments to long options are also
mandatory or optional for the corresponding short option.
-0, --null items are separated by a null, not whitespace;
disables quote and backslash processing and
logical EOF processing
-a, --arg-file=FILE read arguments from FILE, not standard input
-d, --delimiter=CHARACTER items in input stream are separated by CHARACTER,
not by whitespace; disables quote and backslash
...

How to to delete a line given with a variable in sed?

I am attempting to use sed to delete a line, read from user input, from a file whose name is stored in a variable. Right now all sed does is print the line and nothing else.
This is a code snippet of the command I am using:
FILE="/home/devosion/scripts/files/todo.db"
read DELETELINE
sed -e "$DELETELINE"'d' "$FILE"
Is there something I am missing here?
Edit: Switching out the -e option with -i fixed my woes!
You need to delimit the search.
#!/bin/bash
read -r Line
sed "/$Line/d" file
Will delete any line containing the typed input.
Bear in mind that sed matches on regex though and any special characters will be seen as such.
For example searching for 1* will actually delete lines containing any number of 1's not an actual 1 and a star.
Also bear in mind that when the variable expands, it cannot contain the delimiters or the command will break or have unexpexted results.
For example if "$Line" contained "/hello" then the sed command will fail with
sed: -e expression #1, char 4: extra characters after command.
You can either escape the / in this case or use different delimiters.
Personally i would use awk for this
awk -vLine="$Line" '!index($0,Line)' file
Which searches for an exact string and has none of the drawbacks of the sed command.
You might have success with grep instead of sed
read -p "Enter a regex to remove lines: " filter
grep -v "$filter" "$file"
Storing in-place is a little more work:
tmp=$(mktemp)
grep -v "$filter" "$file" > "$tmp" && mv "$tmp" "$file"
or, with sponge (apt install moreutils)
grep -v "$filter" "$file" | sponge "$file"
Note: try to get out of the habit of using ALLCAPSVARS: one day you'll accidentally use PATH=... and then wonder why your script is broken.
I found this, it allows for a range deletion with variables:
#!/bin/bash
lastline=$(whatever you need to do to find the last line)` //or any variation
lines="1,$lastline"
sed -i "$lines"'d' yourfile
keeps it all one util.
Please try this :
sed -i "${DELETELINE}d" $FILE

Using awk in a file that has forward slashes in it

I have a file containing lines similar too ...
/home/test/gp/fish/lib/fish.eye
/home/test/gp/fish/kerf/pl/teeth.eye
I want to take the last string at the end of each line and put it at the start of the line, for example ..
cp fish.eye /home/test/gp/fish/lib/fish.eye
cp teeth.eye /home/test/gp/fish/kerf/pl/teeth.eye
Any help greatly appreciated
Thanks.
Use this for example:
$ awk -F/ '{print "cp", $NF, $0}' your_file
cp fish.eye /home/test/gp/fish/lib/fish.eye
cp teeth.eye /home/test/gp/fish/kerf/pl/teeth.eye
It sets / as field separator, so that the filename is the last field. Then it is a matter of printing accordingly.
Or safer, to handle filenames with spaces and globbing chars, etc (thanks Ed Morton!):
awk -F/ '{printf "cp \"%s\" \"%s\"\n", $NF, $0}' your_file
In bash you can loop through the lines and make use of basename:
while IFS= read -r line
do
echo "cp" "$(basename "$line")" "$line"
#printf "cp %s %s\n" "$(basename "$line")" "$line" <-- this also works
done < your_file
basename returns the strip and suffix from filenames, so that from a name like /path/like/this.sh you get this.sh.
And the one through GNU sed,
$ sed -r 's/^.*\/(.*)$/cp \1 &/' file
cp fish.eye /home/test/gp/fish/lib/fish.eye
cp teeth.eye /home/test/gp/fish/kerf/pl/teeth.eye
Text after last / symbol are fetched and stored into a group. Again in the replacement part, "cp group wholeline" helps to give the above output.
Using bash parameter substitution:
while read -r line; do
echo "cp ${line##*/} $line"
done < file
cp fish.eye /home/test/gp/fish/lib/fish.eye
cp teeth.eye /home/test/gp/fish/kerf/pl/teeth.eye
From the link:
${parameter##word}
The word is expanded to produce a pattern just as in filename expansion
(see Filename Expansion). If the pattern matches the beginning of the expanded value
of parameter, then the result of the expansion is the expanded value of parameter
with the shortest matching pattern (the ‘#’ case) or the longest matching pattern
(the ‘##’ case) deleted.
these three sed one-liners should work too, but awk would be more straightforward:
sed 's/.*/& &/;s#[^ ]*/#cp #' file
and
sed 'h;s#.*/#cp #;G;s/\n/ /' file
and
sed 's#.*/\(.*\)#cp \1 &#' file

Resources