Nhận sai $ LINENO cho chức năng bị mắc kẹt


7

Tôi đang viết một kịch bản Bash cho chính mình để học kịch bản. Tại một số điểm, tôi cần thêm bẫy để xóa các thư mục và tệp không mong muốn nếu tập lệnh bị hủy. Tuy nhiên, vì một số lý do tôi không hiểu, bẫy gọi chức năng dọn dẹp - clean_a()- khi tập lệnh bị giết nhưng $LINENOchỉ vào một dòng trong chính chức năng làm sạch, chứ không phải chức năng - archieve_it()- khi tập lệnh bị giết.

Hành vi dự kiến:

  1. chạy script
  2. Nhấn Ctrl+C
  3. bẫy cache Ctrl+ Cclean_a()chức năng gọi
  4. clean_a()chức năng lặp lại số dòng, mà Ctrl+ Cđược nhấn. Hãy để nó là dòng 10 trong archieve_it().

Điều gì thực sự xảy ra:

  1. chạy script
  2. Nhấn Ctrl+C
  3. bẫy cache Ctrl+ Cclean_a()chức năng gọi
  4. clean_a()lặp lại một số dòng không liên quan. Nói, dòng 25 trong clean_a()chức năng.

Đây là một ví dụ như một phần của kịch bản của tôi:

archieve_it () {
  trap 'clean_a $LINENO $BASH_COMMAND'\
                SIGHUP SIGINT SIGTERM SIGQUIT
  for src in ${sources}; do
   mkdir -p "${dest}${today}${src}"
   if [[ "$?" -ne 0 ]] ; then
    error "Something!" 
   fi
   rsync "${options}" \
         --stats -i \
         --log-file="${dest}${rsync_log}" \
         "${excludes}" "${src}" "${dest}${today}${src}"
  done
}
clean_a () {
  error "something!
  line: $LINENO
  command: $BASH_COMMAND
  removing ${dest}${today}..."
  cd "${dest}"
  rm -rdf "${today}"
  exit "$1"
}

PS: Kịch bản gốc có thể được nhìn thấy ở đây . Định nghĩa và tên biến là trong tiếng Thổ Nhĩ Kỳ. Nếu được yêu cầu, tôi có thể dịch bất cứ thứ gì sang tiếng Anh.

EDIT: Tôi thay đổi kịch bản tốt nhất có thể theo lời giải thích của @ mikeerv như thế này:

#!/bin/bash
PS4='DEBUG: $((LASTNO=$LINENO)) : '; set -x
archieve_it () {
  trap 'clean_a $LASTNO $LINENO $BASH_COMMAND'\
                SIGHUP SIGINT SIGTERM SIGQUIT
  ..
}
clean_a () {
  error " ...
  line: $LINENO $LASTNO
  ..."
}

Bây giờ, nếu tôi chạy tập lệnh với set -xvà chấm dứt nó bằng Ctrl+ C, nó sẽ in số dòng chính xác như có thể thấy bên dưới:

 DDEBUG: 1 : clean_a 1 336 rsync '"${options}"' ...

Tuy nhiên, trong clean_a()chức năng, giá trị của $LASTNOđược in là 1.

 line: 462 1

Nó có liên quan gì đến lỗi được hiển thị bởi @Arkadiusz Drabchot không?

EDIT2 : Tôi đã thay đổi tập lệnh giống như cách @mikerv đề xuất với tôi. Nhưng $ LASTNO trả về 1 là giá trị của dòng khi tập lệnh bị chấm dứt (đáng lẽ nó phải là 337).

#!/bin/bash
PS4='^MDEBUG: $((LASTNO=$LINENO)) : '; set -x
archieve_it () {
  trap 'clean_a $LASTNO $LINENO "$BASH_COMMAND"' \
                SIGHUP SIGINT SIGTERM SIGQUIT
  ...
} 2>/dev/null
clean_a () {
  error " ...
  line: $LASTNO $LINENO
  ..."
} 2>&1

Nếu tôi chạy tập lệnh và chấm dứt tập lệnh bằng Ctrl+ Ctrong khi rsync đang chạy, tôi nhận được kết quả đầu ra này:

^^MDEBUG: 1 : clean_a '337 1 rsync "${options}" --delete-during ...
...
line: 1 465

Như bạn có thể thấy, giá trị của $ LASTNO là 1.

Trong khi tôi đang cố gắng tìm hiểu vấn đề là gì, tôi đã viết một hàm khác - testing- sử dụng định dạng thay thế tham số ${parameter:-default}. Vì vậy, kịch bản hóa ra như thế này:

#!/bin/bash
PS4='^MDEBUG: $((LASTNO=$LINENO)) : '; set -x
archieve_it () {
  trap 'testing "$LASTNO $LINENO $BASH_COMMAND"'\
                 SIGHUP SIGINT SIGTERM SIGQUIT
  ...
} 2>/dev/null
testing() {
  echo -e "${1:-Unknown error!}"
  exit 1
} 2>&1

Bây giờ, nếu tôi chạy tập lệnh và nhấn Ctrl+ C, tôi nhận được kết quả đầu ra này:

^^MDEBUG: 1 : testing '337 1 rsync "${options}" --delete-during ...
337 1 rsync "${options}" --delete-during ... 

337 chỉ ra dòng khi tôi nhấn Ctrl+ C, trong khi rsync đang chạy.

Đối với một bài kiểm tra khác, tôi đã thử viết clear_afuntion như thế này:

clear_a () {
  echo -e " $LASTNO $LINENO"
}

và $ LASTNO vẫn trả lại 1.

Vì vậy, điều này có nghĩa là chúng ta có thể nhận được số dòng chính xác khi tập lệnh kết thúc nếu chúng ta sử dụng thay thế tham số?

EDIT3 Có vẻ như tôi đã áp dụng sai lời giải thích của @ mikeerv trong EDIT2. Tôi đã sửa chữa sai lầm của mình. Tham số vị trí "$1nên được thay thế bằng $ LASTNO trong clear_achức năng.

Đây là kịch bản hoạt động như thế nào tôi muốn nó hoạt động:

#!/bin/bash
PS4='^MDEBUG: $((LASTNO=$LINENO)) : '; set -x
archieve_it () {
  trap 'clean_a $LASTNO $LINENO "$BASH_COMMAND"' \
                SIGHUP SIGINT SIGTERM SIGQUIT
  ...
} 2>/dev/null
clean_a () {
  error " ...
  line: $1
  ..."
} 2>&1

Khi tập lệnh kết thúc, trapđánh giá $LASTNO- đối số thứ nhất -, $LINENO- đối số thứ hai - và $BASH_COMMANDđối số thứ ba -, sau đó chuyển các giá trị của chúng cho clear_ahàm. Cuối cùng, chúng tôi in $ LASTNO với $1số dòng mà đoạn script bị chấm dứt.


Chào! Về điểm 4 - "số dòng không liên quan" - trên hệ thống của tôi, nó luôn trả về 1. Một cái gì đó đã thay đổi trong bash 4 - xem tại đây . Bạn có phiên bản bash nào?
Arkadiusz Drabchot

Xin lỗi, ý tôi là "echo $ 1" luôn mang lại cho tôi 1, không phải "echo $ LINENO"
Arkadiusz Drabc:

Xin chào, tôi sử dụng GNU bash, phiên bản 4.3.22 (1) -release (x86_64-unknown-linux-gnu).
số

@mikeerv, xin lỗi vì sự bất tiện, sai của tôi. Tôi đã sửa chữa sai lầm của mình. Bây giờ nó hoạt động như thế nào tôi muốn làm việc.
số

Bash cũng cung cấp một BASH_LINENOmảng có thể là những gì bạn muốn, thay vì chỉ LINENO.
dimo414

Câu trả lời:


4

Tôi nghĩ vấn đề là bạn đang mong muốn "$LINENO"cung cấp cho bạn dòng thực thi cho lệnh cuối cùng, có thể gần như hoạt động, nhưng clean_a() cũng có chính nó $LINENOvà bạn nên làm thay thế:

error "something!
line: $1
...

Nhưng ngay cả điều đó có thể sẽ không hoạt động bởi vì tôi hy vọng nó sẽ chỉ in dòng mà bạn đặt trap.

Đây là một bản demo nhỏ:

PS4='DEBUG: $LINENO : ' \
bash -x <<\CMD          
    trap 'fn "$LINENO"' EXIT             
    fn() { printf %s\\n "$LINENO" "$1"; }
    echo "$LINENO"
CMD

ĐẦU RA

DEBUG: 1 : trap 'fn "$LINENO"' EXIT
DEBUG: 3 : echo 3
3
DEBUG: 1 : fn 1
DEBUG: 2 : printf '%s\n' 2 1
2
1

Vì vậy, bộ trapđược đặt, sau đó, fn()được xác định, sau đó echođược thực thi. Khi shell hoàn thành thực thi đầu vào của nó, EXITbẫy được chạy và fnđược gọi. Nó được thông qua một đối số - đó là trapdòng $LINENO. fnđầu tiên in ra $LINENOsau đó là đối số đầu tiên của nó.

Tôi có thể nghĩ về một cách bạn có thể có được hành vi mà bạn mong đợi, nhưng nó làm hỏng vỏ của nó stderr:

PS4='DEBUG: $((LASTNO=$LINENO)) : ' \
bash -x <<\CMD
    trap 'fn "$LINENO" "$LASTNO"' EXIT
    fn() { printf %s\\n "$LINENO" "$LASTNO" "$@"; }
    echo "$LINENO"
CMD

ĐẦU RA

DEBUG: 1 : trap 'fn "$LINENO" "$LASTNO"' EXIT
DEBUG: 3 : echo 3
3
DEBUG: 1 : fn 1 3
DEBUG: 2 : printf '%s\n' 2 1 1 3
2
1
1
3

Nó sử dụng $PS4dấu nhắc gỡ lỗi của shell để xác định $LASTNOtrên mỗi dòng được thực thi. Đây là một biến shell hiện tại mà bạn có thể truy cập bất cứ nơi nào trong tập lệnh. Điều đó có nghĩa là cho dù hiện tại dòng nào đang được truy cập, bạn có thể tham khảo dòng gần đây nhất của tập lệnh chạy $LASTNO. Tất nhiên, như bạn có thể thấy, nó đi kèm với đầu ra gỡ lỗi. Bạn có thể đẩy nó đến 2>/dev/nullphần lớn thời gian thực thi của tập lệnh, và sau đó chỉ 2>&1trong clean_a()hoặc một cái gì đó.

Lý do bạn nhận được 1trong $LASTNOlà bởi vì đó giá trị cuối cùng mà $LASTNOđã được thiết lập bởi vì đó là người cuối cùng $LINENOgiá trị. Bạn đã có bạn traptrong archieve_it()chức năng và do đó nó được riêng của mình $LINENOnhư được ghi trong spec dưới đây. Mặc dù nó dường như bashkhông phải là điều đúng ở đó, vì vậy nó cũng có thể là do trapphải thực hiện lại vỏ trên INTtín hiệu và $LINENOdo đó được đặt lại. Tôi hơi mơ hồ về điều đó trong trường hợp này - như là bash, rõ ràng.

Bạn không muốn đánh giá $LASTNOtrong clean_a(), tôi nghĩ. Tốt hơn là đánh giá nó trong trapvà chuyển giá trị trapnhận được $LASTNOthông qua clean_a()như là một đối số. Có lẽ như thế này:

#!/bin/bash
PS4='^MDEBUG: $((LASTNO=$LINENO)) : '; set -x
archieve_it () {
    trap 'clean_a $LASTNO $LINENO "$BASH_COMMAND"' \
        SIGHUP SIGINT SIGTERM SIGQUIT
    while :; do sleep 1; done
} 2>/dev/null
clean_a () { : "$@" ; } 2>&1

Hãy thử điều đó - nó nên làm những gì bạn muốn, tôi nghĩ. Ồ - và lưu ý rằng trong PS4=^Mphần ^Mtrả lại theo nghĩa đen - như CTRL + V ENTER.

Từ thông số kỹ thuật vỏ POSIX :

Được đặt bởi shell thành một số thập phân đại diện cho số dòng liên tiếp hiện tại (được đánh số bắt đầu bằng 1) trong một tập lệnh hoặc hàm trước khi nó thực thi mỗi lệnh. Nếu người dùng hủy cài đặt hoặc đặt lại LINENO, biến có thể mất ý nghĩa đặc biệt đối với tuổi thọ của vỏ. Nếu shell hiện không thực thi tập lệnh hoặc hàm, giá trị của LINENOlà không xác định. Tập này của IEEE Std 1003.1-2001 chỉ định các tác động của biến chỉ cho các hệ thống hỗ trợ tùy chọn Tiện ích di động người dùng.


Cảm ơn bạn rất nhiều @mikeerv vì nhận xét và nỗ lực của bạn.
số

@numand - Tôi chỉ chỉnh sửa của bạn và cố gắng giải quyết nó bằng chính tôi. Nó có giúp gì không?
mikeerv

Thật không may, nó đã không giúp đỡ. Nhưng tôi đã tìm ra thứ gì đó có thể giúp chúng ta.
số

@numand cái gì vậy? và bạn có nghĩa là nó không giúp được gì? nó hoạt động tốt với tôi
mikeerv

1
Tôi cảm ơn bạn rất nhiều. Tôi thực sự đánh giá cao nỗ lực của bạn.
số

7

Giải pháp của mikeerv là tốt nhưng anh ta không chính xác khi nói rằng fnđã vượt qua trapgiới hạn $LINENOkhi bẫy được thực thi. Chèn một dòng trước trap ...và bạn sẽ thấy rằng fntrên thực tế luôn luôn được thông qua 1, bất kể bẫy được khai báo ở đâu.

PS4='DEBUG: $LINENO : ' \
bash -x <<\EOF
    echo Foo
    trap 'fn "$LINENO"' EXIT             
    fn() { printf %s\\n "$LINENO" "$1"; }
    echo "$LINENO"
    exit
EOF

ĐẦU RA

DEBUG: 1 : echo Foo
Foo
DEBUG: 2 : trap 'fn "$LINENO"' EXIT
DEBUG: 4 : echo 4
4
DEBUG: 5 : exit
DEBUG: 1 : fn 1
DEBUG: 3 : printf '%s\n' 3 1
3
1

Vì đối số đầu tiên để bẫy, fn "$LINENO"được đặt trong các dấu ngoặc đơn , $LINENOđược mở rộng , nếu và chỉ khi EXIT nó được kích hoạt và do đó nên mở rộng sang fn 5. Vậy tại sao không? Trong thực tế, nó đã xảy ra, cho đến khi bash-4.0 khi nó được thay đổi có chủ ý để $ LINENO được đặt lại thành 1 khi bẫy được kích hoạt và do đó mở rộng thành fn 1. [nguồn] Tuy nhiên, hành vi ban đầu vẫn được duy trì cho các bẫy ERR, có lẽ là do tần suất trap 'echo "Error at line $LINENO"' ERRsử dụng một cái gì đó như thế nào .

#!/bin/bash

trap 'echo "exit at line $LINENO"' EXIT
trap 'echo "error at line $LINENO"' ERR
false
exit 0

ĐẦU RA

error at line 5
exit at line 1

đã thử nghiệm với GNU bash, phiên bản 4.3.42 (1) -release (x86_64-pc-linux-gnu)


Xin lỗi vì phản hồi muộn @Niklas Holm, và cảm ơn bạn vì thông tin có giá trị này.
20/8/2016
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.