I would like to create a directory name for each month. I know, after some playing with the shell, that:

date -d 1/01 +%b # Gives Jan
date -d 2/01 +%b # Gives Feb
.
date -d 12/01 +%b # Gives Dec

So I have used brace expansion, echo {1..12}/01 and tried to xargs it:

echo {1..12}/01 | xargs -n 1 -I {} date -d {} +%b

But it fails miserably :/ (afterwards I'd like to apply mkdir). How can I do this?

  • 4
    You seem to be over-programming this. If there's a reason, it would help if you explained why you don't simply write 12 lines, "mkdir "Jan"; mkdir "Feb";...". If you need to do it multiple times, put the code in a function, and call as needed. – jamesqf Aug 10 at 17:12
  • 4
    Why are you naming your directories with the names of the months instead of the numbers 01-12? It's gotta be awkward having them lsed in alphabetical order (Apr, Aug, Dec, Feb, Jan, Jul, Jun, Mar, May, Nov, Oct, Sep). – user1024 Aug 10 at 18:21
  • 2
    @jamesqf Why would you do that when you can simply do mkdir january february [...] ? Unless you really need to create them separately there is no need to do it the way you suggested. – Pryftan Aug 10 at 22:17
  • @user1024 I would normally agree with you but I have for one use case the name of the months (lower case though); in another I have 01january etc. Who can tell? There are numerous reasons it might or might be better one way or another. (But yes - if sorting is an issue...) – Pryftan Aug 10 at 22:20
  • @Pryftan: Only a personal preference for shorter lines :-) Also, if I later wanted to change to e.g. "01Jan", it's easier in my editor to do a "c/ / 01/ 12 1" than to do the change on one line. – jamesqf Aug 11 at 16:44
up vote 17 down vote accepted

With -I, xargs gets one argument per line as opposed to the default of one argument per (blank or newline delimited, possibly quoted) word without -I (and implies -n). So in your example date is called only once with {} expanded to the whole output of echo (which is on one line), minus the trailing newline.

Here you can do (note that that -d is a GNU extension):

printf '%s\n' {1..12}/01 | xargs -I {} date -d {} +%b | xargs mkdir --

(note that it won't work correctly in locales where month name abbreviations contain spaces or quote characters; with GNU xargs, you can work around that by using xargs -d '\n' mkdir --)

Now, to get the list of month abbreviations in your locale, querying the locale directly would make more sense:

(IFS=';'; set -o noglob; mkdir -- $(locale abmon))

(see also locale -k LC_TIME to see all the locale data in the LC_TIME category).

Or natively in zsh:

zmodload zsh/langinfo
mkdir -- ${(v)langinfo[(I)ABMON_*]}

At least on GNU systems, in some locales, month abbreviations are padded to fixed width with spaces:

$ LC_ALL=et_EE.UTF-8 locale title abmon
Estonian locale for Estonia
jaan ;veebr;märts;apr  ;mai  ;juuni;juuli;aug  ;sept ;okt  ;nov  ;dets
$ LC_ALL=zh_TW.UTF-8 locale title abmon
Chinese locale for Taiwan R.O.C.
 1月; 2月; 3月; 4月; 5月; 6月; 7月; 8月; 9月;10月;11月;12月

You may want to remove that padding.

The leading spaces would be removed by xargs -I, but not the trailing ones. With zsh:

zmodload zsh/langinfo
set -o extendedglob
mkdir -- ${${${(v)langinfo[(I)ABMON*]}##[[:space:]]#}%%[[:space:]]#}
  • Nice! Did not know about locale abmon. – maulinglawns Aug 10 at 13:25
  • Nice answer, but tl;dr : IFS=';'; set -o noglob; mkdir -- $(locale abmon). ;) – Skippy le Grand Gourou Aug 11 at 11:46

Try a loop?

$ for m in {1..12}; do
> date -d "$m"/01 +%b
> done
jan
feb
mar
apr
maj
jun
jul
aug
sep
okt
nov
dec

If you want to make a directory for each month, I would do something like:

for m in {1..12}; do newdir=$(date -d "$m"/01 +%b); mkdir "$newdir"; done
  • Note that {1..12} is a bashism. For a POSIX shell, use for m in $(seq 1 12)... (though note that seq itself is not mandated by POSIX) – Radovan Garabík Aug 10 at 14:07
  • 2
    @RadovanGarabík, technically, {1..12} is a zshism, copied later by ksh93, bash and several other shells. – Stéphane Chazelas Aug 10 at 14:12

In shells with brace expansion, and date accepting DATAFILE input, try

echo {01..12}/01$'\n' | date -f- +"mkdir %b"

and pipe into shell if happy with the result.

Your command does not work, because of using -I changes the delimiter of xargs:

-I replace-str
Replace occurrences of replace-str in the initial-arguments with names read from standard input. Also, unquoted blanks do not terminate input items; instead the separator is the newline character.

You can add -d " " to xargs to make it work. But you don't even need -I{} in your case:

Try this,

echo {1..12}/01 | xargs -n1 date +%b -d | xargs mkdir
  • 1
    Note that for GNU date to accept options after non-option arguments like that, there must not be a POSIXLY_CORRECT variable in the environment. – Stéphane Chazelas Aug 10 at 12:19

You where so so close.

The problem is that echo is producing a single line 1/01 2/01 3/01 4/01 5/01 6/01 7/01 8/01 9/01 10/01 11/01 12/01, and that xargs is using the newline character as a field separator (not the space).

The solution: Tell echo to put a space between each field.

echo -e {1..12}/01\\n | xargs -n 1 -I {} date -d {} +%b

I only inserted \\n at the end of the echo.

Then to make the directories add |xargs mkdir

e.g.

echo -e {1..12}/01\\n | xargs -n 1 -I {} date -d {} +%b | xargs mkdir

The simplest and most robust way of doing this:

mkdir jan feb mar apr maj jun jul aug sep okt nov dec

It's a static list of months after all...

If you are intent on using GNU date (here assuming an unmodified $IFS and GNU date like for your date -d):

mkdir $( printf '%s\n' {1..12}/01 | date -f - +%b )
  • 2
    Except that it doesn't make month name abbreviations in the language of the user. – Stéphane Chazelas Aug 10 at 13:11
  • @StéphaneChazelas Unless they are Swedish, like me :-) – Kusalananda Aug 10 at 18:34

Your Answer

 

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.