Windows CRLF to Unix LF Issues in Cygwin Shell Script

เมื่อเรารัน shell script ของโปรแกรม Cygwin for Windows ซึ่งมีการเขียนคำสั่งไปตัดเอาข้อความผ่านคำสั่ง (Command Line) ของ Windows มาใส่ในตัวแปรของ shell script

เช่น ในตัวอย่างนี้คือคำสั่ง ipconfig เมื่อได้ข้อความที่ต้องการมาเราจะได้ \r แถมมาให้ด้วยต่อท้าย เพราะ Windows style line ending จะมี CRLF (\r\n)  ในขณะที่ Linux style line ending จะมี LF (\n) เท่านั้น

น่าแปลกใจมากว่า เราเคยรัน shell script นี้ใน Windows 7 ใช้งานได้ แต่พอเป็น Windows 10 build 1709 มันรันไม่ได้

 

ปัญหา

เมื่อเปิด Cygwin Terminal ขึ้นมา จะได้เป็น bash shell

ในไฟล์ test.sh ดังตัวอย่างข้างล่างนี้ เมื่อสั่งรัน จะพบว่าพบข้อผิดพลาด ตัวแปร ZONEX จะไม่มีค่า ซึ่งจริง ๆ จะต้องได้คำว่า zone1
$ cat test.sh

#!/bin/bash

DHCPSERVER=$(ipconfig /all | grep -i "DHCP Server" | cut -d: -f2 | xargs)

MAC=$(ipconfig /all | grep -A4 -i "^Ethernet Adapter Ethernet" | tail -1 | cut -d\: -f2 | tr - : | xargs)

ZONEX=$(curl -s http://${DHCPSERVER}/dhcpd.txt | grep -i ${MAC} | awk '{print $2}' | cut -d'_' -f1)

echo "DHCP SERVER is ${DHCPSERVER}"
echo "MAC is ${MAC}"
echo "Zone is ${ZONEX}"

สั่งรันดูผลลัพธ์ด้วยคำสั่ง bash test.sh

$ bash test.sh
DHCP SERVER is 192.168.6.150
MAC is 50:7B:9D:30:2E:4B
Zone is

ผมก็ตรวจสอบด้วยวิธีการ debug คือ เพิ่ม -x ดังตัวอย่างนี้

$ bash -x test.sh
 ++ ipconfig /all
 ++ grep -i 'DHCP Server'
 ++ cut -d: -f2
 ++ xargs
 + DHCPSERVER=$'192.168.6.150\r'
 ++ ipconfig /all
 ++ grep -A4 -i '^Ethernet Adapter Ethernet'
 ++ tail -1
 ++ cut -d: -f2
 ++ tr - :
 ++ xargs
 + MAC=$'50:7B:9D:30:2E:4B\r'
 ++ curl -s $'http://192.168.6.150\r/dhcpd.txt'
 ++ grep -i $'50:7B:9D:30:2E:4B\r'
 ++ awk '{print $2}'
 ++ cut -d_ -f1
 + ZONEX=
 ' echo 'DHCP SERVER is 192.168.6.150
 DHCP SERVER is 192.168.6.150
 ' echo 'MAC is 50:7B:9D:30:2E:4B
 MAC is 50:7B:9D:30:2E:4B
 + echo 'Zone is '
 Zone is

จึงพบว่า ตัวแปร DHCPSERVER และ ตัวแปร MAC จะมี “/r” แถมมาให้ด้วย ซึ่งเป็นส่วนเกินที่ทำให้คำสั่งถัดไปทำงานผิดพลาดทำให้ได้ค่า ZONEX เป็น ว่างเปล่า

 

วิธีแก้ไข เราต้องใส่ option ” -o igncr ” หลังคำสั่ง bash เพื่อให้ตัด “/r” ออกไป

สั่งรันดูผลลัพธ์ด้วยคำสั่ง bash -o igncr test.sh

ผลลัพธ์คราวนี้ถูกต้องแล้ว

$ bash -o igncr test.sh
DHCP SERVER is 192.168.6.150
MAC is 50:7B:9D:30:2E:4B
Zone is zone1

 

วิธีแก้อีกวิธีคือ หากไม่ใส่ option ที่ว่านี้ เราก็ต้องไปแก้ไขบรรทัดคำสั่ง เพื่อเติม sed ‘s/\r$//’ ต่อท้าย เป็นการตัด /r ออกไปก่อนนำค่าที่ได้ไปใส่ในตัวแปร DHCPSERVER
เช่น

DHCPSERVER=$(ipconfig /all | grep -i "DHCP Server" | cut -d: -f2 | xargs | sed 's/\r$//')

ซึ่งก็ทำได้เช่นกัน แต่ต้องแก้ไขไฟล์ shell script ซึ่งก็แล้วแต่ชอบวิธีไหน

 

Reference
https://stackoverflow.com/users/1010997/user1010997
https://stackoverflow.com/questions/11616835/r-command-not-found-bashrc-bash-profile
https://stackoverflow.com/questions/18608380/r-command-not-found