5
5
# https://github.com/carlesfe/bashblog/contributors
6
6
# Check out README.md for more details
7
7
8
+ # Some shell settings for robustness by default. These help eliminate
9
+ # unexpected snags and security vulnerabilities in case someone forgets to
10
+ # quote a variable somewhere. They do require a few coding adaptations.
11
+
12
+ IFS=$' \n ' # Globally, we do word splitting only on newline (which also
13
+ # makes "$*" expand with newline separator instead of space).
14
+
15
+ set -f # Disable globbing (pathname expansion). It can be re-enabled
16
+ # locally using 'set +f'; it's handy to do this in a subshell,
17
+ # for example in $(command substitution), as the globbing will
18
+ # be local to the subshell.
19
+
8
20
# Global variables
9
21
# It is recommended to perform a 'rebuild' after changing any of this in the code
10
22
@@ -257,6 +269,14 @@ get_html_file_content() {
257
269
}"
258
270
}
259
271
272
+ # Invoke the editor specified by the $EDITOR environment variable. Use a
273
+ # function for this as we need to locally word-split $EDITOR on spaces
274
+ # (in case it contains arguments, like EDITOR=' joe -nobackups) .
275
+ invoke_editor () {
276
+ local IFS=$' \t\n '
277
+ $EDITOR " $1 "
278
+ }
279
+
260
280
# Edit an existing, published .html file while keeping its original timestamp
261
281
# Please note that this function does not automatically republish anything, as
262
282
# it is usually called from 'main'.
@@ -276,7 +296,8 @@ edit() {
276
296
touch_timestamp=$( LC_ALL=C date -r " ${1%% .* } .html" +" $date_format_timestamp " )
277
297
tags_before=$( tags_in_post " ${1%% .* } .html" )
278
298
if [[ $2 == full ]]; then
279
- $EDITOR " $1 "
299
+ invoke_editor " $1 "
300
+ touch -t " $touch_timestamp " " $1 "
280
301
filename=$1
281
302
else
282
303
if [[ ${1##* .} == md ]]; then
@@ -286,7 +307,8 @@ edit() {
286
307
exit
287
308
fi
288
309
# editing markdown file
289
- $EDITOR " $1 "
310
+ invoke_editor " $1 "
311
+ touch -t " $touch_timestamp " " $1 "
290
312
TMPFILE=$( markdown " $1 " )
291
313
filename=${1%% .* } .html
292
314
else
@@ -296,27 +318,29 @@ edit() {
296
318
get_post_title " $1 " > " $TMPFILE "
297
319
# Post text with plaintext tags
298
320
get_html_file_content ' text' ' text' < " $1 " | sed " /^<p>$template_tags_line_header /s|<a href='$prefix_tags \([^']*\).html'>\\ 1</a>|\\ 1|g" >> " $TMPFILE "
299
- $EDITOR "$TMPFILE"
321
+ invoke_editor " $TMPFILE "
300
322
filename=$1
301
323
fi
302
324
rm " $filename "
303
325
if [[ $2 == keep ]]; then
326
+ old_filename=' '
304
327
parse_file " $TMPFILE " " $edit_timestamp " " $filename "
305
328
else
329
+ old_filename=$filename # save old filename to exclude it from $relevant_posts
306
330
parse_file " $TMPFILE " " $edit_timestamp " # this command sets $filename as the html processed file
307
331
[[ ${1##* .} == md ]] && mv " $1 " " ${filename%% .* } .md" 2> /dev/null
308
332
fi
309
333
rm " $TMPFILE "
334
+ touch -t " $touch_timestamp " " $filename "
310
335
fi
311
- touch -t "$touch_timestamp" "$filename"
312
- touch -t "$touch_timestamp" "$1"
313
336
chmod 644 " $filename "
314
337
echo " Posted $filename "
315
338
tags_after=$( tags_in_post " $filename " )
316
- relevant_tags=$(echo "$tags_before $tags_after" | tr ' ,' ' ' | tr ' ' ' \n ' | sort -u | tr ' \n ' ' ' )
317
- if [[ ! -z $relevant_tags ]]; then
318
- relevant_posts="$(posts_with_tags $relevant_tags) $filename"
319
- rebuild_tags "$relevant_posts" "$relevant_tags"
339
+ relevant_tags=$( sort -u <<< " $tags_before" $' \n ' " $tags_after " )
340
+ if [[ -n $relevant_tags ]]; then
341
+ relevant_posts=$( posts_with_tags $relevant_tags ) $' \n ' $filename
342
+ [[ -n $old_filename ]] && relevant_posts=$( grep -vFx " $old_filename " <<< " $relevant_posts" )
343
+ rebuild_tags $relevant_posts --tags $relevant_tags
320
344
fi
321
345
}
322
346
@@ -488,10 +512,11 @@ create_html_page() {
488
512
parse_file () {
489
513
# Read for the title and check that the filename is ok
490
514
title=" "
491
- while IFS= ' ' read -r line; do
515
+ while read -r line; do
492
516
if [[ -z $title ]]; then
493
517
# remove extra <p> and </p> added by markdown
494
- title=$( echo " $line " | sed ' s/<\/*p>//g' )
518
+ title=${line# <p>}
519
+ title=${title% </ p>}
495
520
if [[ -n $3 ]]; then
496
521
filename=$3
497
522
else
@@ -513,7 +538,6 @@ parse_file() {
513
538
elif [[ $line == " <p>$template_tags_line_header " * ]]; then
514
539
tags=$( echo " $line " | cut -d " :" -f 2- | sed -e ' s/<\/p>//g' -e ' s/^ *//' -e ' s/ *$//' -e ' s/, /,/g' )
515
540
IFS=, read -r -a array <<< " $tags"
516
-
517
541
echo -n " <p>$template_tags_line_header " >> " $content "
518
542
for item in " ${array[@]} " ; do
519
543
echo -n " <a href='$prefix_tags$item .html'>$item </a>, "
578
602
filename=" "
579
603
while [[ $post_status != " p" && $post_status != " P" ]]; do
580
604
[[ -n $filename ]] && rm " $filename " # Delete the generated html file, if any
581
- $EDITOR " $TMPFILE "
605
+ invoke_editor " $TMPFILE "
582
606
if [[ $fmt == md ]]; then
583
607
html_from_md=$( markdown " $TMPFILE " )
584
608
parse_file " $html_from_md "
620
644
echo " Posted $filename "
621
645
relevant_tags=$( tags_in_post $filename )
622
646
if [[ -n $relevant_tags ]]; then
623
- relevant_posts=" $( posts_with_tags $relevant_tags ) $ filename"
624
- rebuild_tags " $relevant_posts " " $relevant_tags "
647
+ relevant_posts=$( posts_with_tags $relevant_tags ) $' \n ' $ filename
648
+ rebuild_tags $relevant_posts --tags $relevant_tags
625
649
fi
626
650
}
627
651
@@ -636,7 +660,7 @@ all_posts() {
636
660
{
637
661
echo " <h3>$template_archive_title </h3>"
638
662
prev_month=" "
639
- while IFS= ' ' read -r i ; do
663
+ for i in $( set +f ; ls -t ./ * .html ) ; do
640
664
is_boilerplate_file " $i " && continue
641
665
echo -n " ." 1>&3
642
666
# Month headers
@@ -653,7 +677,7 @@ all_posts() {
653
677
# Date
654
678
date=$( LC_ALL=$date_locale date -r " $i " +" $date_format " )
655
679
echo " $date </li>"
656
- done < <( ls -t ./ * .html )
680
+ done
657
681
echo " " 1>&3
658
682
echo " </ul>"
659
683
echo " <div id=\" all_posts\" ><a href=\" ./$index_file \" >$template_archive_index_page </a></div>"
@@ -676,7 +700,7 @@ all_tags() {
676
700
{
677
701
echo " <h3>$template_tags_title </h3>"
678
702
echo " <ul>"
679
- for i in $prefix_tags * .html; do
703
+ for i in $( set +f ; printf ' %s\n ' $ prefix_tags* .html) ; do
680
704
[[ -f " $i " ]] || break
681
705
echo -n " ." 1>&3
682
706
nposts=$( grep -c " <\!-- text begin -->" " $i " )
@@ -713,7 +737,8 @@ rebuild_index() {
713
737
# Create the content file
714
738
{
715
739
n=0
716
- while IFS=' ' read -r i; do
740
+ for i in $( set +f; ls -t ./* .html) # sort by date, newest first
741
+ do
717
742
is_boilerplate_file " $i " && continue ;
718
743
if (( n >= number_of_index_articles)) ; then break ; fi
719
744
if [[ -n $cut_do ]]; then
@@ -723,7 +748,7 @@ rebuild_index() {
723
748
fi
724
749
echo -n " ." 1>&3
725
750
n=$(( n + 1 ))
726
- done < <( ls -t ./ * .html ) # sort by date, newest first
751
+ done
727
752
728
753
feed=$blog_feed
729
754
if [[ -n $global_feedburner ]]; then feed=$global_feedburner ; fi
@@ -740,14 +765,22 @@ rebuild_index() {
740
765
741
766
# Finds all tags referenced in one post.
742
767
# Accepts either filename as first argument, or post content at stdin
743
- # Prints one line with space-separated tags to stdout
768
+ # Prints tags to stdout, one per line.
769
+ # (Since we're doing global IFS word splitting on newline only,
770
+ # something like 'for tag in $(tags_in_post $i)' will work.)
744
771
tags_in_post () {
745
- sed -n " /^<p>$template_tags_line_header /{s/^<p>$template_tags_line_header //;s/<[^>]*>//g;s/[ ,]\+/ /g;p;}" " $1 " | tr ' , ' ' '
772
+ local newline=$' \n '
773
+ sed -n " /^<p>$template_tags_line_header / {
774
+ s/^<p>$template_tags_line_header [[:blank:]]*//
775
+ s/[[:blank:]]*<[^>]*>[[:blank:]]*//g
776
+ s/[[:blank:]]*,[[:blank:]]*/\\ $newline /g
777
+ p
778
+ }" " $1 "
746
779
}
747
780
748
781
# Finds all posts referenced in a number of tags.
749
- # Arguments are tags
750
- # Prints one line with space-separated tags to stdout
782
+ # Arguments are tags.
783
+ # Prints file names to stdout, one per line.
751
784
posts_with_tags () {
752
785
(( $# < 1 )) && return
753
786
set -- " ${@/#/ $prefix_tags } "
@@ -758,58 +791,61 @@ posts_with_tags() {
758
791
# Rebuilds tag_*.html files
759
792
# if no arguments given, rebuilds all of them
760
793
# if arguments given, they should have this format:
761
- # " FILE1 [FILE2 [...]]" " TAG1 [TAG2 [...]]"
794
+ # FILE1 [FILE2 [...]] --tags TAG1 [TAG2 [...]]
762
795
# where FILEn are files with posts which should be used for rebuilding tags,
763
796
# and TAGn are names of tags which should be rebuilt.
764
797
# example:
765
- # rebuild_tags "one_post.html another_article.html" "example-tag another-tag"
766
- # mind the quotes!
798
+ # rebuild_tags one_post.html another_article.html --tags example-tag another-tag
767
799
rebuild_tags () {
768
- if (( $# < 2 )) ; then
800
+ if (( $# < 1 )) ; then
769
801
# will process all files and tags
770
- files=$( ls -t ./* .html)
802
+ files=( $( set +f ; ls -t ./* .html) )
771
803
all_tags=yes
772
804
else
773
805
# will process only given files and tags
774
- files=$( printf ' %s\n' $1 | sort -u)
775
- files=$( ls -t $files )
776
- tags=$2
806
+ for (( i= 1 ; i<= $# ; i++ )) ; do
807
+ [[ ${! i} == --tags ]] && break
808
+ done
809
+ files=( $( ls -t $( sort -u <<< " ${*:1:$((i-1))}" ) ) )
810
+ tags=( " ${@: $((i+1)): $# } " )
811
+ all_tags=' '
777
812
fi
778
813
echo -n " Rebuilding tag pages "
779
814
n=0
780
815
if [[ -n $all_tags ]]; then
781
- rm ./" $prefix_tags " * .html & > /dev/null
816
+ ( set +f ; rm -f ./" $prefix_tags " * .html )
782
817
else
783
- for i in $ tags; do
784
- rm " ./$prefix_tags$i .html" & > /dev/null
818
+ for i in " ${ tags[@]} " ; do
819
+ rm -f " ./$prefix_tags$i .html"
785
820
done
786
821
fi
787
822
# First we will process all files and create temporal tag files
788
823
# with just the content of the posts
789
824
tmpfile=tmp.$RANDOM
790
825
while [[ -f $tmpfile ]]; do tmpfile=tmp.$RANDOM ; done
791
- while IFS= ' ' read -r i ; do
792
- is_boilerplate_file " $i " && continue ;
826
+ for i in " ${files[@]} " ; do
827
+ is_boilerplate_file " $i " && continue
793
828
echo -n " ."
794
829
if [[ -n $cut_do ]]; then
795
830
get_html_file_content ' entry' ' entry' ' cut' < " $i " | awk " /$cut_line / { print \" <p class=\\\" readmore\\\" ><a href=\\\" $i \\\" >$template_read_more </a></p>\" ; next } 1"
796
831
else
797
832
get_html_file_content ' entry' ' entry' < " $i "
798
833
fi > " $tmpfile "
799
834
for tag in $( tags_in_post " $i " ) ; do
800
- if [[ -n $all_tags || " $tags " == * " $tag " * ]]; then
835
+ # if either all tags or array tags[] contains $tag...
836
+ if [[ -n $all_tags || $' \n ' " ${tags[*]} " $' \n ' == * $' \n ' " $tag " $' \n ' * ]]; then
801
837
cat " $tmpfile " >> " $prefix_tags$tag " .tmp.html
802
838
fi
803
839
done
804
- done <<< " $files "
840
+ done
805
841
rm " $tmpfile "
806
842
# Now generate the tag files with headers, footers, etc
807
- while IFS= ' ' read -r i ; do
843
+ for i in $( set +f ; ls -t ./ " $prefix_tags " * .tmp.html 2> /dev/null ) ; do
808
844
tagname=${i# ./ " $prefix_tags " }
809
845
tagname=${tagname% .tmp.html}
810
846
create_html_page " $i " " $prefix_tags$tagname .html" yes " $global_title — $template_tag_title \" $tagname \" " " $global_author "
811
847
rm " $i "
812
- done < <( ls -t ./ " $prefix_tags " * .tmp.html 2> /dev/null )
848
+ done
813
849
echo
814
850
}
815
851
@@ -833,11 +869,12 @@ get_post_author() {
833
869
list_tags () {
834
870
if [[ $2 == -n ]]; then do_sort=1; else do_sort=0; fi
835
871
836
- ls ./$prefix_tags * .html & > /dev/null
837
- (( $? != 0 )) && echo " No posts yet. Use 'bb.sh post' to create one" && return
872
+ if ! (set +f; set -- $prefix_tags * .html; [[ -e $1 ]]); then
873
+ echo " No posts yet. Use 'bb.sh post' to create one"
874
+ return
875
+ fi
838
876
839
- lines=" "
840
- for i in $prefix_tags * .html; do
877
+ for i in $( set +f; printf ' %s\n' $prefix_tags * .html) ; do
841
878
[[ -f " $i " ]] || break
842
879
nposts=$( grep -c " <\!-- text begin -->" " $i " )
843
880
tagname=${i# " $prefix_tags " }
@@ -856,17 +893,19 @@ list_tags() {
856
893
857
894
# Displays a list of the posts
858
895
list_posts () {
859
- ls ./* .html & > /dev/null
860
- (( $? != 0 )) && echo " No posts yet. Use 'bb.sh post' to create one" && return
896
+ if ! (set +f; set -- * .html; [[ -e $1 ]]); then
897
+ echo " No posts yet. Use 'bb.sh post' to create one"
898
+ return
899
+ fi
861
900
862
901
lines=" "
863
902
n=1
864
- while IFS= ' ' read -r i ; do
903
+ for i in $( set +f ; ls -t ./ * .html ) ; do
865
904
is_boilerplate_file " $i " && continue
866
905
line=" $n # $( get_post_title " $i " ) # $( LC_ALL=$date_locale date -r " $i " +" $date_format " ) "
867
906
lines+=$line \\ n
868
907
n=$(( n + 1 ))
869
- done < <( ls -t ./ * .html )
908
+ done
870
909
871
910
echo -e " $lines " | column -t -s " #"
872
911
}
@@ -889,7 +928,7 @@ make_rss() {
889
928
echo " <atom:link href=\" $global_url /$blog_feed \" rel=\" self\" type=\" application/rss+xml\" />"
890
929
891
930
n=0
892
- while IFS= ' ' read -r i ; do
931
+ for i in $( set +f ; ls -t ./ * .html ) ; do
893
932
is_boilerplate_file " $i " && continue
894
933
(( n >= number_of_feed_articles)) && break # max 10 items
895
934
echo -n " ." 1>&3
@@ -903,7 +942,7 @@ make_rss() {
903
942
echo " <pubDate>$( LC_ALL=C date -r " $i " +" $date_format_full " ) </pubDate></item>"
904
943
905
944
n=$(( n + 1 ))
906
- done < <( ls -t ./ * .html )
945
+ done
907
946
908
947
echo ' </channel></rss>'
909
948
} 3>&1 > " $rssfile "
@@ -1002,7 +1041,7 @@ create_css() {
1002
1041
rebuild_all_entries () {
1003
1042
echo -n " Rebuilding all entries "
1004
1043
1005
- for i in ./ * .html; do
1044
+ for i in $( set +f ; printf ' %s\n ' * .html) ; do
1006
1045
is_boilerplate_file " $i " && continue ;
1007
1046
contentfile=.tmp.$RANDOM
1008
1047
while [[ -f $contentfile ]]; do contentfile=.tmp.$RANDOM ; done
@@ -1058,7 +1097,7 @@ reset() {
1058
1097
echo " Are you sure you want to delete all blog entries? Please write \" Yes, I am!\" "
1059
1098
read -r line
1060
1099
if [[ $line == " Yes, I am!" ]]; then
1061
- rm .* .html ./* .html ./* .css ./* .rss & > /dev/null
1100
+ (set +f ; rm -f .* .html ./* .html ./* .css ./* .rss)
1062
1101
echo
1063
1102
echo " Deleted all posts, stylesheets and feeds."
1064
1103
echo " Kept your old '.backup.tar.gz' just in case, please delete it manually if needed."
@@ -1130,9 +1169,9 @@ do_main() {
1130
1169
fi
1131
1170
1132
1171
# Test for existing html files
1133
- if ls ./ * .html & > /dev/null ; then
1172
+ if (set +f ; set -- * .html; [[ -e $1 ]]) ; then
1134
1173
# We're going to back up just in case
1135
- tar -c -z -f " .backup.tar.gz" -- * .html &&
1174
+ (set +f ; tar -c -z -f " .backup.tar.gz" -- * .html) &&
1136
1175
chmod 600 " .backup.tar.gz"
1137
1176
elif [[ $1 == rebuild ]]; then
1138
1177
echo " Can't find any html files, nothing to rebuild"
0 commit comments