Reputation: 165
I want to read the first six characters of a text file into a string and prepend every other non-blank line in that file with that string. An example of such file could be:
04/17 Walmart .toys $ 70 .cash $ -70
Caltex .gas 20 $ .cheque $ -20
McDonalds .burger 1 $ .cash $ -1
Each entry, i.e.: each non-empty line, needs a date, which for reasons of easy data entry has been entered on the first line only. Entries are separated by 1 or more empty lines. The output would look like this:
04/17 Walmart .toys $ 70 .cash $ -70
04/17 Caltex .gas 20 $ .cheque $ -20
04/17 McDonalds .burger 1 $ .cash $ -1
I can match the non empty strings with things like ^[^@]+[ ]*.[ ]([^;{}:]+)[ ]*$, but I don't know how to actually implement that for non blank lines.
This Bash script looks attractive to me, but I don't know how then to insert my string at the start.
I also can't find the straight answer to my question on Stack Overflow.
I tried a script which accepts a file name:
read -n 6 date < $1
sed 's/^/$(echo $date)/' | \
sed 's/^$(echo $date)\n//' | > $newName
I was able to come up with prepending the date with space (e.g. the string: '04/17 ') to every line, and then remove the same from every line in which nothing follows.
However, it seems that sed doesn't accept the command substitution:
sed: -e expression #1, char 10: unknown option to `s'
Upvotes: 4
Views: 619
Reputation: 63912
Using Perl:
perl -plE 'if($.==1){$d=substr($_,0,6);next}elsif(/./){s/^/$d/}' file > new
Output
04/17 Walmart .toys $ 70 .cash $ -70
04/17 Caltex .gas 20 $ .cheque $ -20
04/17 McDonalds .burger 1 $ .cash $ -1
Or in same file with backup to file.bak
perl -i.bak -plE 'if($.==1){$d=substr($_,0,6);next}elsif(/./){s/^/$d/}' file
Or same file without backup
perl -i -plE 'if($.==1){$d=substr($_,0,6);next}elsif(/./){s/^/$d/}' file
Or, if you're unsure about the leading zeros in the date,
perl -plE 'if($.==1){($d)=m|^(\d+/\d+\s)|;next}elsif(/./){s/^/$d/}' file
will match any digit(s) / digit(s) space
on the beginning of the 1st line.
As l'L'l mentioned in comment, the above adds the date to pseudo-empty lines too (where the line only looks-like empty) e.g. it contains at least one space. In such case, instead of /./
:
/\w/
- so, prepend the date only to lines containing at least one word character;/\S/
- when containing at least one non-space characterExplanation:
perl -plE ' # Run the commands on every input line and print them.
if( $. == 1) { # If it is the 1st line
$d = substr($_, 0, 6); # take the first 6 characters and store it to $d
next # And continue to the next line.
}
elsif( /\S/ ) { # Else if the line contains any nonspace character
s/^/$d/ # add to the beginning the content of $d
}
' file > new
Upvotes: 3
Reputation: 33327
The slash is terminating the sed command, change the delimiter to something else:
"s#^#$(echo $date)#"
You could probably write like this too:
"s#^#$date#"
Note however that this approach is generally fragile (as you have discovered), because you can't treat the variable as a literal string.
Based on the samples on the updated question, I would suggest to use a single awk command to do your text processing. Something like this could give you the sample output:
$ cat file
04/17 Walmart .toys $ 70 .cash $ -70
Caltex .gas 20 $ .cheque $ -20
McDonalds .burger 1 $ .cash $ -1
$ awk 'NR==1{d=$1}NR>1&&NF>0{$0=d" "$0}1' file
04/17 Walmart .toys $ 70 .cash $ -70
04/17 Caltex .gas 20 $ .cheque $ -20
04/17 McDonalds .burger 1 $ .cash $ -1
Upvotes: 2
Reputation: 47169
You should be able to do this with one sed
command:
read -rn 6 date < "$1"
sed -E 's#^([a-zA-Z]+)#'"$date"' \1#g' "$1" > newfile
The capture group ensures there's at least one character on the line before inserting the date.
EDIT: Based on the revision to your question:
newfile="output.txt"
lineone=$(head -1 "$1");
read -rn 6 date <<< "$lineone"
sed -E 's#^([a-zA-Z]+)#'"$date"' \1#g; 1s#^.*$#'"$lineone"'#' "$1" > "$newfile"
Since you aren't doing an in-place edit, you can do the $date insertions, and then go back and swap out the first line since it would end up with two dates. There might be "better" ways to do this such as using Perl
, or losing the second sed
command, although this should at least give you a basic idea though on how it works...
Result (newfile):
04/17 Walmart .toys $ 70 .cash $ -70
04/17 Caltex .gas 20 $ .cheque $ -20
04/17 McDonalds .burger 1 $ .cash $ -1
NOTE: In some versions of
sed
the option for extended regex can either be-r
or-E
.
Upvotes: 4
Reputation: 8406
Pure bash
answer:
unset n
while read -r x ; do
case "${#n}$x" in 6) ;; 6*) x="$n$x" ;; *) n="${x:0:6}" ;; esac
echo "$x"
done < file > newfile
Output:
04/17 Walmart .toys $ 70 .cash $ -70
04/17 Caltex .gas 20 $ .cheque $ -20
04/17 McDonalds .burger 1 $ .cash $ -1
Upvotes: 3