10

I need to create the file /opt/nginx/conf.d/default.conf with this content via shell script and create the file if it doesn't exist:

server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name _;
    root /usr/share/nginx/html;
}

How do I write multiline content via a shell script?

I created the directory

sudo mkdir -p /opt/nginx/conf.d

But I don't know how to write a file.

3

3 Answers 3

21

summary : use >> to append, use [ -f file ] to test.

try

if [ ! -f myfile ]
then
   cat <<EOF > myfile
server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name $server ;
    root /usr/share/nginx/html;
}
EOF
fi
  • the syntax cat <<EOF is called a "here document".
  • $server will be replace by its value, or empty if undefined.
  • as pointed out, you can use single quoted 'EOF' to avoid replacing var if any.
  • you can also have multiple echo (this could be painfull to maintain if too many echo)

    echo "## foo.conf" > foo.conf
    echo param1=hello >> foo.conf
    echo param2=world >> foo.conf
    

prepending

there is no direct prepend in bash, either use temporary file

mv file file_tmp
cat new_content file_tmp > file
rm file_tmp

or edit it

sed -i -e '1r new_file' -e 'wq' file
5
  • How would I prepend some multiline content to a file? Oct 4, 2017 at 14:05
  • 1
    Another wise change to do would be to quote the here-doc limit strings, so that any words containing $ (if present) are not treated as variables and therefore not getting expanded
    – Inian
    Oct 4, 2017 at 14:08
  • @user3142695 The easiest would be to write the here-document to a separate file and then cat the two files together and rename the result.
    – Kusalananda
    Oct 4, 2017 at 14:12
  • If one wants to do many separate echos, it's still more efficient to have just one redirection, by putting them in a block: { echo "foo"; echo "bar"; echo "baz"; } >file -- that way we aren't re-opening file every time we just want to write a single line to it, and closing it when that line is done. Oct 5, 2017 at 1:46
  • Another good use of here document: cat << EOF | sudo tee /opt/nginx/conf.d/default.conf ...
    – lp1051
    May 2, 2018 at 14:04
4

If the /opt/nginx/conf.d/default.conf file does not exist, then print(f) the static string into the file:

[ -f /opt/nginx/conf.d/default.conf ] || printf 'server {\n    listen 80 default_server;\n    listen [::]:80 default_server;\n    server_name _;\n    root /usr/share/nginx/html;\n}\n' > /opt/nginx/conf.d/default.conf
1
  • 1
    I'd consider printf '%s\n' "first line" "second line" perhaps a bit more readable; that way one doesn't need to read through the whole string to figure out where the escapes are.. Oct 5, 2017 at 1:47
1

There's nothing stopping you passing newline characters to echo. Inside single or double quotes, in Bourne-like shells, newline is not any more special than any other characters¹

if [ ! -e "$file" ]; then
  echo "server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name $server;
    root /usr/share/nginx/html;
}" > "$file"
fi

Note that we're not adding the last line delimiter as echo adds on itself.

If the text to output may contain backslashes or start with -, you may want to use printf instead. Either as printf '%s\n' "text" so a trailing newline is added automatically like for echo or printf %s and add the newline in the text to be output:

if [ ! -e "$file" ]; then
  printf %s "server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name $server;
    root /usr/share/nginx/html;
}
" > "$file"
fi

With printf '%s\n' ... you can also pass more than one argument to printf will will be printed each with an added newline characters, so you can do:

new_lines=(
  'server {'
  '  listen 80 default_server;'
  '  listen [::]:80 default_server;'
  "  server_name $server;"
  '  root /usr/share/nginx/html;'
  '}'
)
[ -e "$file" ] || printf '%s\n' "${new_lines[@]}" > "$file"

Where you can decide the kind of quoting to use for each line.

Beware though that if passed no argument printf '%s\n' will still print one empty line. In zsh, you can use print -rC1 -- instead. Or you could define a:

println() {
  [ "$#" -eq 0 ] || printf '%s\n' "$@"
}

helper function.

Also note the noclobber option of POSIX shells that causes > redirections operators to fail if that target exists as a regular file.

So:

set -o noblobber # same as set -C
printf '%s\n' "$newcontent" > "$file"

Would make sure $newcontent is not written to the $file if it already existed (as a regular file; if it were a device (like a symlink to /dev/null) or a directory or fifo, etc, $newcontent would still be written there; for that reason it doesn't fully avoid the TOCTOU race condition as the shell still needs to check the type of the file first).

To avoid writing to a file that already exists, regardless of its type, in a race-free way, you can use zsh which can let you open files with the O_EXCL flag:

zmodload zsh/system
(sysopen -w -o excl -u 1 -- $file && print -r -- $newcontent > $file)

¹ Except when preceded by a backslash inside double quotes.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .