I've been spending quite a lot of time working this problem from here out and I've managed to get the correct output without leaning on xargs calling bash (which the tutorial hasn't covered). It took a while to get the quoting right with the child bash call within xargs and for some reason I had to save the replace-str in a variable as well as recalculate the date and save it in a new variable because:

EDIT: I understand that rename can be used to solve this as well, but that was not yet covered in the tutorial I linked and not the point of this exercise (which, as stated before, is to use xargs)

  • apparently param expansion doesn't apply to the replace-str = {} so I can't do "{}%%.*" and get the file extension
  • apparently even a double quoted variable, today, will be null if used within the child bash process run within an xargs command.

    # %<conversions> come from 'man 3 strftime'                                                                                                                  
    today=$( date "+%Y-%m-%d" )

    # make prefix date copy                                                                                                                                      
    #ls "$@" | xargs -I ^ cp ^ "${today}_^"                                                                                                                        

    #make suffix date copy                                                                                                                                                                                                                                                       
    echo "$@" | sed "s; ;\n;g" | xargs -I {} \
                          bash -cs 'var="{}"; td=$( date "+%Y-%m-%d" ); \
                                    echo "${var%%.*}_${td}.${var##*.}"'
    #prints basename_.bash i.e. without date
    echo "$@" | sed "s; ;\n;g" | xargs -I {} \
                          bash -cs 'var="{}"; \
                                    echo "${var%%.*}_${today}.${var##*.}"'

will output

    s1_2021-03-11.bash s2_2021-03-11.bash 

on input

    ./myscript s1.bash s2.bash

EDIT: To reiterate the input will look like

    ./myscript file1.ext file2.ext file3.ext ... fileN.ext

and after running ls there should exist files that look like

    file1_yyyy-mm-dd.ext ... fileN_yyyy-mm-dd.ext

If possible I would rather find a solution that DOES NOT use bash -c because the tutorial on the website has not yet covered that and so I doubt it was intended to be a solution to the problem. I'd also like to understand my confusion about param expansion no working with xargs and the need for another date variable.

  • I suggest you look up the rename command. Unfortunately, there are at least two quite different versions of it out there, but they should both allow you to rename large numbers of files with ease rather than getting into complicated shell scripting. For example, my version uses regular expressions. This here rename "s/$/.$today/" * appends the date to all filenames. Commented Mar 12, 2021 at 2:27
  • The other type of rename takes a static string and renames it. It is much less flexible. rename . _$today. * replaces the dot in all filenames with _$today.. This doesn't work as intended when a filename contains more than one dot; you may want to do it by extension then: rename .bash _$today.bash *. Commented Mar 12, 2021 at 2:35
  • @berndbausch Thanks for the comment, I actually had developed up a version that used rename but the tutorial hadn't covered that yet and specifically mentioned xargs in the problem statement (trying to get me used to using it I guess) so I have decided to forego it's use. I'll add a comment in the question to disallow rename. Regardless I appreciate it!
    – z.karl
    Commented Mar 12, 2021 at 2:45
  • It will be good if you also wrote the input and the expected output.
    – guest_7
    Commented Mar 12, 2021 at 6:21
  • @guest_7 I thought I did wrote the expected input and output but in hindsight it might not have been clear enough I will edit the question with it clearer.
    – z.karl
    Commented Mar 12, 2021 at 7:09

1 Answer 1


If we do not have access to GNU version of xargs/sed , then we need to take the responsibility to quote filenames safe for xargs.


 ./myscript   your list of files goes here

# user defined function: qXargs
# backslashes all chars special to xargs:
# SPC/TAB/NL/double quotes, single quotes, and backslash.

qXargs() {
  printf '%s\n' "$1" |
  sed \
    -e "s:[\\'${IFS%?}\"]:\\\\&:g" \
    -e '$!s:$:\\:'  \

# loop over command-line arguments
# quote them to make xargs safe and
# break apart arg into head portion and
#'extension and slip in today's date

today=$(date +'%Y-%d-%m')

for arg
   src=$(qXargs "$arg")
  head=$(qXargs "${arg%.*}")
  tail=$(qXargs "${arg##*.}")
  printf '%s\n%s_%s.%s\n'  \
    "$src" \
    "$head" "$today" "$tail" ;
done | xargs -n 2 -t mv -f --

Assuming GNU versions of utilities.

### this is the ./myscript file
d=$(date +'%Y-%d-%m')
printf '%s\0' "$@" |
sed -Ez "p;s/(.*)(\..*)/\1$d\2/" |
xargs -r0 -n2 -t mv -f --


  • Your confusion regarding not being able to get the extension from the xargs replacement string {} is bcoz {} is just a placeholder to remind xargs to replace it with the argument. So the shell while parsing the xargs command cannot see it.
  • Really appreciate the answer, I made edits to clarify the comment you made before about the expected input and output. Regarding parsing ls I don't think I do, I parse echo, though I see you might have noticed I parse ls in a commented out section. That's super interesting about printf though! I'll make sure to see if I can work something out with it.
    – z.karl
    Commented Mar 12, 2021 at 7:20
  • the output on running with your solution was tr: Illegal byte sequence tr: Illegal byte sequence sed: illegal option -- z but honestly this is so much more complicated than I think it needs to be. This challenge question is from an intro to scripting tutorial and with all due respect your solution seems to be overkill.
    – z.karl
    Commented Mar 12, 2021 at 8:08
  • Actually the solution is the last 3 lines. The rest of the paraphernalia is just to create random file names. Now your sed doesn't take -z means you are not using GNU version of it.
    – guest_7
    Commented Mar 12, 2021 at 8:21
  • Well, I can't complain. The solution works and it doesn't require a call to bash. I'll wait a little to see if anyone else adds a non-GNU version that's a little more beginner, but otherwise I'll accept your answer. Thanks for the response! It sent me down some interesting rabbit holes.
    – z.karl
    Commented Mar 13, 2021 at 0:43

You must log in to answer this question.

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